mirror of
https://github.com/OpenMW/openmw.git
synced 2025-03-01 13:39:42 +00:00
Merge pull request #1198 from Allofich/combat
Make combat engagement logic more like vanilla
This commit is contained in:
commit
2ea0db1d1a
10 changed files with 256 additions and 130 deletions
|
@ -325,22 +325,38 @@ namespace MWClass
|
||||||
|
|
||||||
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
|
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.
|
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||||
|
|
||||||
if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker))
|
// NOTE: 'object' and/or 'attacker' may be empty.
|
||||||
getCreatureStats(ptr).setAttacked(true);
|
if (!attacker.isEmpty() && !stats.getAiSequence().isInCombat(attacker))
|
||||||
|
stats.setAttacked(true);
|
||||||
|
|
||||||
// Self defense
|
// Self defense
|
||||||
bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits.
|
bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits.
|
||||||
|
|
||||||
// No retaliation for totally static creatures (they have no movement or attacks anyway)
|
// No retaliation for totally static creatures (they have no movement or attacks anyway)
|
||||||
if (isMobile(ptr) && !attacker.isEmpty())
|
if (isMobile(ptr) && !attacker.isEmpty())
|
||||||
{
|
|
||||||
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
||||||
|
|
||||||
|
// Attacker and target store each other as hitattemptactor if they have no one stored yet
|
||||||
|
if (!attacker.isEmpty() && !ptr.isEmpty())
|
||||||
|
{
|
||||||
|
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
||||||
|
// First handle the attacked actor
|
||||||
|
if ((stats.getHitAttemptActorId() == -1)
|
||||||
|
&& (statsAttacker.getAiSequence().isInCombat(ptr)
|
||||||
|
|| attacker == MWMechanics::getPlayer()))
|
||||||
|
stats.setHitAttemptActorId(statsAttacker.getActorId());
|
||||||
|
|
||||||
|
// Next handle the attacking actor
|
||||||
|
if ((statsAttacker.getHitAttemptActorId() == -1)
|
||||||
|
&& (statsAttacker.getAiSequence().isInCombat(ptr)
|
||||||
|
|| attacker == MWMechanics::getPlayer()))
|
||||||
|
statsAttacker.setHitAttemptActorId(stats.getActorId());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId());
|
stats.setLastHitAttemptObject(object.getCellRef().getRefId());
|
||||||
|
|
||||||
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||||
{
|
{
|
||||||
|
@ -358,7 +374,7 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId());
|
stats.setLastHitObject(object.getCellRef().getRefId());
|
||||||
|
|
||||||
if (damage > 0.0f && !object.isEmpty())
|
if (damage > 0.0f && !object.isEmpty())
|
||||||
MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
|
MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
|
||||||
|
@ -371,16 +387,13 @@ namespace MWClass
|
||||||
if (!attacker.isEmpty())
|
if (!attacker.isEmpty())
|
||||||
{
|
{
|
||||||
// Check for knockdown
|
// Check for knockdown
|
||||||
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat();
|
float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat();
|
||||||
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
|
float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
|
||||||
* getGmst().iKnockDownOddsMult->getInt() * 0.01f + getGmst().iKnockDownOddsBase->getInt();
|
* getGmst().iKnockDownOddsMult->getInt() * 0.01f + getGmst().iKnockDownOddsBase->getInt();
|
||||||
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
|
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
|
||||||
{
|
stats.setKnockedDown(true);
|
||||||
getCreatureStats(ptr).setKnockedDown(true);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
|
stats.setHitRecovery(true); // Is this supposed to always occur?
|
||||||
}
|
}
|
||||||
|
|
||||||
damage = std::max(1.f, damage);
|
damage = std::max(1.f, damage);
|
||||||
|
@ -395,15 +408,15 @@ namespace MWClass
|
||||||
|
|
||||||
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
|
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
|
||||||
|
|
||||||
MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
|
MWMechanics::DynamicStat<float> health(stats.getHealth());
|
||||||
health.setCurrent(health.getCurrent() - damage);
|
health.setCurrent(health.getCurrent() - damage);
|
||||||
getCreatureStats(ptr).setHealth(health);
|
stats.setHealth(health);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
|
MWMechanics::DynamicStat<float> fatigue(stats.getFatigue());
|
||||||
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
||||||
getCreatureStats(ptr).setFatigue(fatigue);
|
stats.setFatigue(fatigue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -654,22 +654,38 @@ namespace MWClass
|
||||||
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
|
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();
|
||||||
|
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||||
// NOTE: 'object' and/or 'attacker' may be empty.
|
bool wasDead = stats.isDead();
|
||||||
|
|
||||||
bool wasDead = getCreatureStats(ptr).isDead();
|
|
||||||
|
|
||||||
// Note OnPcHitMe is not set for friendly hits.
|
// Note OnPcHitMe is not set for friendly hits.
|
||||||
bool setOnPcHitMe = true;
|
bool setOnPcHitMe = true;
|
||||||
if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker))
|
|
||||||
{
|
|
||||||
getCreatureStats(ptr).setAttacked(true);
|
|
||||||
|
|
||||||
|
// NOTE: 'object' and/or 'attacker' may be empty.
|
||||||
|
if (!attacker.isEmpty() && !stats.getAiSequence().isInCombat(attacker))
|
||||||
|
{
|
||||||
|
stats.setAttacked(true);
|
||||||
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attacker and target store each other as hitattemptactor if they have no one stored yet
|
||||||
|
if (!attacker.isEmpty() && !ptr.isEmpty())
|
||||||
|
{
|
||||||
|
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
||||||
|
// First handle the attacked actor
|
||||||
|
if ((stats.getHitAttemptActorId() == -1)
|
||||||
|
&& (statsAttacker.getAiSequence().isInCombat(ptr)
|
||||||
|
|| attacker == MWMechanics::getPlayer()))
|
||||||
|
stats.setHitAttemptActorId(statsAttacker.getActorId());
|
||||||
|
|
||||||
|
// Next handle the attacking actor
|
||||||
|
if ((statsAttacker.getHitAttemptActorId() == -1)
|
||||||
|
&& (statsAttacker.getAiSequence().isInCombat(ptr)
|
||||||
|
|| attacker == MWMechanics::getPlayer()))
|
||||||
|
statsAttacker.setHitAttemptActorId(stats.getActorId());
|
||||||
|
}
|
||||||
|
|
||||||
if (!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId());
|
stats.setLastHitAttemptObject(object.getCellRef().getRefId());
|
||||||
|
|
||||||
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||||
{
|
{
|
||||||
|
@ -687,7 +703,7 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId());
|
stats.setLastHitObject(object.getCellRef().getRefId());
|
||||||
|
|
||||||
|
|
||||||
if (damage > 0.0f && !object.isEmpty())
|
if (damage > 0.0f && !object.isEmpty())
|
||||||
|
@ -706,21 +722,16 @@ namespace MWClass
|
||||||
|
|
||||||
int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->getInt();
|
int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->getInt();
|
||||||
if (Misc::Rng::roll0to99() < chance)
|
if (Misc::Rng::roll0to99() < chance)
|
||||||
{
|
|
||||||
MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
|
MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
|
||||||
}
|
|
||||||
|
|
||||||
// Check for knockdown
|
// Check for knockdown
|
||||||
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat();
|
float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat();
|
||||||
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
|
float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
|
||||||
* gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt();
|
* gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt();
|
||||||
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
|
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
|
||||||
{
|
stats.setKnockedDown(true);
|
||||||
getCreatureStats(ptr).setKnockedDown(true);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
|
stats.setHitRecovery(true); // Is this supposed to always occur?
|
||||||
|
|
||||||
if (damage > 0 && ishealth)
|
if (damage > 0 && ishealth)
|
||||||
{
|
{
|
||||||
|
@ -799,13 +810,13 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
|
MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
|
||||||
health.setCurrent(health.getCurrent() - damage);
|
health.setCurrent(health.getCurrent() - damage);
|
||||||
getCreatureStats(ptr).setHealth(health);
|
stats.setHealth(health);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
|
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
|
||||||
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
||||||
getCreatureStats(ptr).setFatigue(fatigue);
|
stats.setFatigue(fatigue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wasDead && getCreatureStats(ptr).isDead())
|
if (!wasDead && getCreatureStats(ptr).isDead())
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
|
|
||||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||||
|
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/inventorystore.hpp"
|
#include "../mwworld/inventorystore.hpp"
|
||||||
|
@ -280,7 +282,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer)
|
void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer)
|
||||||
{
|
{
|
||||||
const CreatureStats& creatureStats1 = actor1.getClass().getCreatureStats(actor1);
|
CreatureStats& creatureStats1 = actor1.getClass().getCreatureStats(actor1);
|
||||||
if (creatureStats1.getAiSequence().isInCombat(actor2))
|
if (creatureStats1.getAiSequence().isInCombat(actor2))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -298,97 +300,124 @@ namespace MWMechanics
|
||||||
if (!actor1.getClass().isMobile(actor1))
|
if (!actor1.getClass().isMobile(actor1))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Start combat if target actor is in combat with one of our followers or escorters
|
// If this is set to true, actor1 will start combat with actor2 if the awareness check at the end of the method returns true
|
||||||
const std::list<MWWorld::Ptr>& followersAndEscorters = getActorsSidingWith(actor1);
|
bool aggressive = false;
|
||||||
for (std::list<MWWorld::Ptr>::const_iterator it = followersAndEscorters.begin(); it != followersAndEscorters.end(); ++it)
|
|
||||||
{
|
|
||||||
// Need to check both ways since player doesn't use AI packages
|
|
||||||
if ((creatureStats2.getAiSequence().isInCombat(*it)
|
|
||||||
|| it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(actor2))
|
|
||||||
&& !creatureStats1.getAiSequence().isInCombat(*it))
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start combat if target actor is in combat with someone we are following through a follow package
|
// Get actors allied with actor1. Includes those following or escorting actor1, actors following or escorting those actors, (recursive)
|
||||||
for (std::list<MWMechanics::AiPackage*>::const_iterator it = creatureStats1.getAiSequence().begin(); it != creatureStats1.getAiSequence().end(); ++it)
|
// and any actor currently being followed or escorted by actor1
|
||||||
{
|
std::set<MWWorld::Ptr> allies1;
|
||||||
if (!(*it)->sideWithTarget())
|
getActorsSidingWith(actor1, allies1);
|
||||||
continue;
|
|
||||||
|
|
||||||
MWWorld::Ptr followTarget = (*it)->getTarget();
|
// If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and actor2
|
||||||
|
for (std::set<MWWorld::Ptr>::const_iterator it = allies1.begin(); it != allies1.end(); ++it)
|
||||||
if (followTarget.isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (creatureStats1.getAiSequence().isInCombat(followTarget))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Need to check both ways since player doesn't use AI packages
|
|
||||||
if (creatureStats2.getAiSequence().isInCombat(followTarget)
|
|
||||||
|| followTarget.getClass().getCreatureStats(followTarget).getAiSequence().isInCombat(actor2))
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start combat with the player if we are already in combat with a player follower or escorter
|
|
||||||
const std::list<MWWorld::Ptr>& playerFollowersAndEscorters = getActorsSidingWith(getPlayer());
|
|
||||||
if (againstPlayer)
|
|
||||||
{
|
|
||||||
for (std::list<MWWorld::Ptr>::const_iterator it = playerFollowersAndEscorters.begin(); it != playerFollowersAndEscorters.end(); ++it)
|
|
||||||
{
|
{
|
||||||
if (creatureStats1.getAiSequence().isInCombat(*it))
|
if (creatureStats1.getAiSequence().isInCombat(*it))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (creatureStats2.matchesActorId(it->getClass().getCreatureStats(*it).getHitAttemptActorId()))
|
||||||
{
|
{
|
||||||
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
|
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
|
||||||
|
// Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat
|
||||||
|
// if the player gets out of reach, while the ally would continue combat with the player
|
||||||
|
creatureStats1.setHitAttemptActorId(it->getClass().getCreatureStats(*it).getHitAttemptActorId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's been no attack attempt yet but an ally of actor1 is in combat with actor2, become aggressive to actor2
|
||||||
|
if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(actor2))
|
||||||
|
aggressive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<MWWorld::Ptr> playerFollowersAndEscorters;
|
||||||
|
getActorsSidingWith(MWMechanics::getPlayer(), playerFollowersAndEscorters);
|
||||||
|
|
||||||
|
bool isPlayerFollowerOrEscorter = std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor1) != playerFollowersAndEscorters.end();
|
||||||
|
|
||||||
|
// If actor2 and at least one actor2 are in combat with actor1, actor1 and its allies start combat with them
|
||||||
|
// Doesn't apply for player followers/escorters
|
||||||
|
if (!aggressive && !isPlayerFollowerOrEscorter)
|
||||||
|
{
|
||||||
|
// Check that actor2 is in combat with actor1
|
||||||
|
if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(actor1))
|
||||||
|
{
|
||||||
|
std::set<MWWorld::Ptr> allies2;
|
||||||
|
getActorsSidingWith(actor2, allies2);
|
||||||
|
// Check that an ally of actor2 is also in combat with actor1
|
||||||
|
for (std::set<MWWorld::Ptr>::const_iterator it = allies2.begin(); it != allies2.end(); ++it)
|
||||||
|
{
|
||||||
|
if ((it)->getClass().getCreatureStats(*it).getAiSequence().isInCombat(actor1))
|
||||||
|
{
|
||||||
|
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
|
||||||
|
// Also have actor1's allies start combat
|
||||||
|
for (std::set<MWWorld::Ptr>::const_iterator it2 = allies1.begin(); it2 != allies1.end(); ++it2)
|
||||||
|
MWBase::Environment::get().getMechanicsManager()->startCombat(*it2, actor2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, don't initiate combat with an unreachable target
|
// If set in the settings file, player followers and escorters will become aggressive toward enemies in combat with them or the player
|
||||||
|
if (!aggressive && isPlayerFollowerOrEscorter && Settings::Manager::getBool("followers attack on sight", "Game"))
|
||||||
|
{
|
||||||
|
if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(actor1))
|
||||||
|
aggressive = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (std::set<MWWorld::Ptr>::const_iterator it = allies1.begin(); it != allies1.end(); ++it)
|
||||||
|
{
|
||||||
|
if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(*it))
|
||||||
|
{
|
||||||
|
aggressive = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop here if target is unreachable
|
||||||
if (!MWMechanics::canFight(actor1, actor2))
|
if (!MWMechanics::canFight(actor1, actor2))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool aggressive = false;
|
// Do aggression check if actor2 is the player or a player follower or escorter
|
||||||
|
if (!aggressive)
|
||||||
|
{
|
||||||
if (againstPlayer || std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor2) != playerFollowersAndEscorters.end())
|
if (againstPlayer || std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor2) != playerFollowersAndEscorters.end())
|
||||||
{
|
{
|
||||||
// Player followers and escorters with high fight should not initiate combat here with the player or with
|
// Player followers and escorters with high fight should not initiate combat with the player or with
|
||||||
// other player followers or escorters
|
// other player followers or escorters
|
||||||
if (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor1) != playerFollowersAndEscorters.end())
|
if (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor1) == playerFollowersAndEscorters.end())
|
||||||
return;
|
|
||||||
|
|
||||||
aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2);
|
aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Make guards fight aggressive creatures
|
|
||||||
if (!actor1.getClass().isNpc() && actor2.getClass().isClass(actor2, "Guard"))
|
|
||||||
{
|
|
||||||
if (creatureStats1.getAiSequence().isInCombat() && MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2))
|
|
||||||
aggressive = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter
|
||||||
|
if (actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc())
|
||||||
|
{
|
||||||
|
bool followerOrEscorter = false;
|
||||||
|
for (std::list<MWMechanics::AiPackage*>::const_iterator it = creatureStats2.getAiSequence().begin(); it != creatureStats2.getAiSequence().end(); ++it)
|
||||||
|
{
|
||||||
|
// The follow package must be first or have nothing but combat before it
|
||||||
|
if ((*it)->sideWithTarget())
|
||||||
|
{
|
||||||
|
followerOrEscorter = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!followerOrEscorter && creatureStats2.getAiSequence().isInCombat())
|
||||||
|
aggressive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the above conditions turned actor1 aggressive towards actor2, do an awareness check. If it passes, start combat with actor2.
|
||||||
if (aggressive)
|
if (aggressive)
|
||||||
{
|
{
|
||||||
bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2);
|
bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2);
|
||||||
|
|
||||||
if (againstPlayer || std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor2) != playerFollowersAndEscorters.end())
|
|
||||||
LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1);
|
LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1);
|
||||||
|
|
||||||
if (LOS)
|
if (LOS)
|
||||||
{
|
|
||||||
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
|
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
|
||||||
if (!againstPlayer) // start combat between each other
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getMechanicsManager()->startCombat(actor2, actor1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -931,7 +960,10 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
static const int iCrimeThresholdMultiplier = esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->getInt();
|
static const int iCrimeThresholdMultiplier = esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->getInt();
|
||||||
if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier)
|
if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier)
|
||||||
|
{
|
||||||
MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player);
|
MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player);
|
||||||
|
creatureStats.setHitAttemptActorId(player.getClass().getCreatureStats(player).getActorId()); // Stops the guard from quitting combat if player is unreachable
|
||||||
|
}
|
||||||
else
|
else
|
||||||
creatureStats.getAiSequence().stack(AiPursue(player), ptr);
|
creatureStats.getAiSequence().stack(AiPursue(player), ptr);
|
||||||
creatureStats.setAlarmed(true);
|
creatureStats.setAlarmed(true);
|
||||||
|
@ -1054,10 +1086,25 @@ namespace MWMechanics
|
||||||
if (iter->first == player)
|
if (iter->first == player)
|
||||||
iter->second->getCharacterController()->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell());
|
iter->second->getCharacterController()->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell());
|
||||||
|
|
||||||
|
// If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player.
|
||||||
|
if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead()
|
||||||
|
|| !iter->first.getClass().getCreatureStats(iter->first).getAiSequence().isInCombat()
|
||||||
|
|| !inProcessingRange))
|
||||||
|
{
|
||||||
|
iter->first.getClass().getCreatureStats(iter->first).setHitAttemptActorId(-1);
|
||||||
|
if (player.getClass().getCreatureStats(player).getHitAttemptActorId() == iter->first.getClass().getCreatureStats(iter->first).getActorId())
|
||||||
|
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MWWorld::Ptr playerHitAttemptActor = MWBase::Environment::get().getWorld()->searchPtrViaActorId(player.getClass().getCreatureStats(player).getHitAttemptActorId());
|
||||||
|
|
||||||
|
if (!playerHitAttemptActor.isInCell())
|
||||||
|
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
|
||||||
|
|
||||||
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
|
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
|
||||||
{
|
{
|
||||||
MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports
|
|
||||||
bool cellChanged = MWBase::Environment::get().getWorld()->hasCellChanged();
|
bool cellChanged = MWBase::Environment::get().getWorld()->hasCellChanged();
|
||||||
|
MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports
|
||||||
updateActor(actor, duration);
|
updateActor(actor, duration);
|
||||||
if (!cellChanged && MWBase::Environment::get().getWorld()->hasCellChanged())
|
if (!cellChanged && MWBase::Environment::get().getWorld()->hasCellChanged())
|
||||||
{
|
{
|
||||||
|
@ -1470,18 +1517,36 @@ namespace MWMechanics
|
||||||
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
||||||
{
|
{
|
||||||
const MWWorld::Class &cls = iter->first.getClass();
|
const MWWorld::Class &cls = iter->first.getClass();
|
||||||
CreatureStats &stats = cls.getCreatureStats(iter->first);
|
const CreatureStats &stats = cls.getCreatureStats(iter->first);
|
||||||
if (stats.isDead())
|
if (stats.isDead())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// An actor counts as following if AiFollow or AiEscort is the current AiPackage, or there are only Combat packages before the AiFollow/AiEscort package
|
// An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat packages before the Follow/Escort package
|
||||||
for (std::list<MWMechanics::AiPackage*>::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it)
|
for (std::list<MWMechanics::AiPackage*>::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it)
|
||||||
{
|
{
|
||||||
if ((*it)->sideWithTarget() && (*it)->getTarget() == actor)
|
if ((*it)->sideWithTarget() && (*it)->getTarget() == actor)
|
||||||
|
{
|
||||||
list.push_back(iter->first);
|
list.push_back(iter->first);
|
||||||
|
break;
|
||||||
|
}
|
||||||
else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat)
|
else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Actors that are targeted by this actor's Follow or Escort packages also side with them
|
||||||
|
if (actor != getPlayer())
|
||||||
|
{
|
||||||
|
const CreatureStats &stats2 = actor.getClass().getCreatureStats(actor);
|
||||||
|
for (std::list<MWMechanics::AiPackage*>::const_iterator it2 = stats2.getAiSequence().begin(); it2 != stats2.getAiSequence().end(); ++it2)
|
||||||
|
{
|
||||||
|
if ((*it2)->sideWithTarget() && !(*it2)->getTarget().isEmpty())
|
||||||
|
{
|
||||||
|
list.push_back((*it2)->getTarget());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if ((*it2)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
@ -1551,8 +1616,6 @@ namespace MWMechanics
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
std::list<MWWorld::Ptr> Actors::getActorsFighting(const MWWorld::Ptr& actor) {
|
std::list<MWWorld::Ptr> Actors::getActorsFighting(const MWWorld::Ptr& actor) {
|
||||||
std::list<MWWorld::Ptr> list;
|
std::list<MWWorld::Ptr> list;
|
||||||
std::vector<MWWorld::Ptr> neighbors;
|
std::vector<MWWorld::Ptr> neighbors;
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "combat.hpp"
|
#include "combat.hpp"
|
||||||
#include "coordinateconverter.hpp"
|
#include "coordinateconverter.hpp"
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
|
#include "mechanicsmanagerimp.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -233,9 +234,14 @@ namespace MWMechanics
|
||||||
storage.stopAttack();
|
storage.stopAttack();
|
||||||
characterController.setAttackingOrSpell(false);
|
characterController.setAttackingOrSpell(false);
|
||||||
storage.mActionCooldown = 0.f;
|
storage.mActionCooldown = 0.f;
|
||||||
if (target == MWMechanics::getPlayer())
|
// Continue combat if target is player or player follower/escorter and an attack has been attempted
|
||||||
|
const std::list<MWWorld::Ptr>& playerFollowersAndEscorters = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(MWMechanics::getPlayer());
|
||||||
|
bool targetSidesWithPlayer = (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target) != playerFollowersAndEscorters.end());
|
||||||
|
if ((target == MWMechanics::getPlayer() || targetSidesWithPlayer)
|
||||||
|
&& ((actor.getClass().getCreatureStats(actor).getHitAttemptActorId() == target.getClass().getCreatureStats(target).getActorId())
|
||||||
|
|| (target.getClass().getCreatureStats(target).getHitAttemptActorId() == actor.getClass().getCreatureStats(actor).getActorId())))
|
||||||
forceFlee = true;
|
forceFlee = true;
|
||||||
else
|
else // Otherwise end combat
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace MWMechanics
|
||||||
mTalkedTo (false), mAlarmed (false), mAttacked (false),
|
mTalkedTo (false), mAlarmed (false), mAttacked (false),
|
||||||
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
|
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
|
||||||
mHitRecovery(false), mBlock(false), mMovementFlags(0),
|
mHitRecovery(false), mBlock(false), mMovementFlags(0),
|
||||||
mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1),
|
mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1),
|
||||||
mDeathAnimation(-1), mTimeOfDeath(), mLevel (0)
|
mDeathAnimation(-1), mTimeOfDeath(), mLevel (0)
|
||||||
{
|
{
|
||||||
for (int i=0; i<4; ++i)
|
for (int i=0; i<4; ++i)
|
||||||
|
@ -371,6 +371,16 @@ namespace MWMechanics
|
||||||
return mLastHitAttemptObject;
|
return mLastHitAttemptObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CreatureStats::setHitAttemptActorId(int actorId)
|
||||||
|
{
|
||||||
|
mHitAttemptActorId = actorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CreatureStats::getHitAttemptActorId() const
|
||||||
|
{
|
||||||
|
return mHitAttemptActorId;
|
||||||
|
}
|
||||||
|
|
||||||
void CreatureStats::addToFallHeight(float height)
|
void CreatureStats::addToFallHeight(float height)
|
||||||
{
|
{
|
||||||
mFallHeight += height;
|
mFallHeight += height;
|
||||||
|
@ -521,6 +531,7 @@ namespace MWMechanics
|
||||||
state.mActorId = mActorId;
|
state.mActorId = mActorId;
|
||||||
state.mDeathAnimation = mDeathAnimation;
|
state.mDeathAnimation = mDeathAnimation;
|
||||||
state.mTimeOfDeath = mTimeOfDeath.toEsm();
|
state.mTimeOfDeath = mTimeOfDeath.toEsm();
|
||||||
|
state.mHitAttemptActorId = mHitAttemptActorId;
|
||||||
|
|
||||||
mSpells.writeState(state.mSpells);
|
mSpells.writeState(state.mSpells);
|
||||||
mActiveSpells.writeState(state.mActiveSpells);
|
mActiveSpells.writeState(state.mActiveSpells);
|
||||||
|
@ -569,6 +580,7 @@ namespace MWMechanics
|
||||||
mActorId = state.mActorId;
|
mActorId = state.mActorId;
|
||||||
mDeathAnimation = state.mDeathAnimation;
|
mDeathAnimation = state.mDeathAnimation;
|
||||||
mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath);
|
mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath);
|
||||||
|
mHitAttemptActorId = state.mHitAttemptActorId;
|
||||||
|
|
||||||
mSpells.readState(state.mSpells);
|
mSpells.readState(state.mSpells);
|
||||||
mActiveSpells.readState(state.mActiveSpells);
|
mActiveSpells.readState(state.mActiveSpells);
|
||||||
|
|
|
@ -62,6 +62,8 @@ namespace MWMechanics
|
||||||
int mGoldPool;
|
int mGoldPool;
|
||||||
|
|
||||||
int mActorId;
|
int mActorId;
|
||||||
|
int mHitAttemptActorId; // Stores an actor that attacked this actor. Only one is stored at a time,
|
||||||
|
// and it is not changed if a different actor attacks. It is cleared when combat ends.
|
||||||
|
|
||||||
// The index of the death animation that was played, or -1 if none played
|
// The index of the death animation that was played, or -1 if none played
|
||||||
signed char mDeathAnimation;
|
signed char mDeathAnimation;
|
||||||
|
@ -241,9 +243,11 @@ namespace MWMechanics
|
||||||
bool getStance (Stance flag) const;
|
bool getStance (Stance flag) const;
|
||||||
|
|
||||||
void setLastHitObject(const std::string &objectid);
|
void setLastHitObject(const std::string &objectid);
|
||||||
void setLastHitAttemptObject(const std::string &objectid);
|
|
||||||
const std::string &getLastHitObject() const;
|
const std::string &getLastHitObject() const;
|
||||||
|
void setLastHitAttemptObject(const std::string &objectid);
|
||||||
const std::string &getLastHitAttemptObject() const;
|
const std::string &getLastHitAttemptObject() const;
|
||||||
|
void setHitAttemptActorId(const int actorId);
|
||||||
|
int getHitAttemptActorId() const;
|
||||||
|
|
||||||
// Note, this is just a cache to avoid checking the whole container store every frame. We don't need to store it in saves.
|
// Note, this is just a cache to avoid checking the whole container store every frame. We don't need to store it in saves.
|
||||||
// TODO: Put it somewhere else?
|
// TODO: Put it somewhere else?
|
||||||
|
|
|
@ -1214,48 +1214,51 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MechanicsManager::actorAttacked(const MWWorld::Ptr &ptr, const MWWorld::Ptr &attacker)
|
bool MechanicsManager::actorAttacked(const MWWorld::Ptr &target, const MWWorld::Ptr &attacker)
|
||||||
{
|
{
|
||||||
if (ptr == getPlayer())
|
std::list<MWWorld::Ptr> followersAttacker = getActorsSidingWith(attacker);
|
||||||
|
std::list<MWWorld::Ptr> followersTarget = getActorsSidingWith(target);
|
||||||
|
|
||||||
|
MWMechanics::CreatureStats& statsTarget = target.getClass().getCreatureStats(target);
|
||||||
|
|
||||||
|
if (target == getPlayer())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::list<MWWorld::Ptr> followers = getActorsSidingWith(attacker);
|
if (std::find(followersAttacker.begin(), followersAttacker.end(), target) != followersAttacker.end())
|
||||||
MWMechanics::CreatureStats& targetStats = ptr.getClass().getCreatureStats(ptr);
|
|
||||||
if (std::find(followers.begin(), followers.end(), ptr) != followers.end())
|
|
||||||
{
|
{
|
||||||
targetStats.friendlyHit();
|
statsTarget.friendlyHit();
|
||||||
|
|
||||||
if (targetStats.getFriendlyHits() < 4)
|
if (statsTarget.getFriendlyHits() < 4)
|
||||||
{
|
{
|
||||||
MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
|
MWBase::Environment::get().getDialogueManager()->say(target, "hit");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attacking an NPC that is already in combat with any other NPC is not a crime
|
// Attacking an NPC that is already in combat with any other NPC is not a crime
|
||||||
AiSequence& seq = targetStats.getAiSequence();
|
AiSequence& seq = statsTarget.getAiSequence();
|
||||||
bool isFightingNpc = false;
|
bool isFightingNpc = false;
|
||||||
for (std::list<AiPackage*>::const_iterator it = seq.begin(); it != seq.end(); ++it)
|
for (std::list<AiPackage*>::const_iterator it = seq.begin(); it != seq.end(); ++it)
|
||||||
{
|
{
|
||||||
if ((*it)->getTypeId() == AiPackage::TypeIdCombat)
|
if ((*it)->getTypeId() == AiPackage::TypeIdCombat)
|
||||||
{
|
{
|
||||||
MWWorld::Ptr target = (*it)->getTarget();
|
MWWorld::Ptr target2 = (*it)->getTarget();
|
||||||
if (!target.isEmpty() && target.getClass().isNpc())
|
if (!target2.isEmpty() && target2.getClass().isNpc())
|
||||||
isFightingNpc = true;
|
isFightingNpc = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ptr.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker)
|
if (target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker)
|
||||||
&& !isAggressive(ptr, attacker) && !isFightingNpc)
|
&& !isAggressive(target, attacker) && !isFightingNpc)
|
||||||
commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault);
|
commitCrime(attacker, target, MWBase::MechanicsManager::OT_Assault);
|
||||||
|
|
||||||
if (!attacker.isEmpty() && (attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr)
|
if (!attacker.isEmpty() && (attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(target)
|
||||||
|| attacker == getPlayer())
|
|| attacker == getPlayer())
|
||||||
&& !seq.isInCombat(attacker))
|
&& !seq.isInCombat(attacker))
|
||||||
{
|
{
|
||||||
// Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back.
|
// Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back.
|
||||||
// Note: accidental or collateral damage attacks are ignored.
|
// Note: accidental or collateral damage attacks are ignored.
|
||||||
startCombat(ptr, attacker);
|
startCombat(target, attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1365,6 +1368,7 @@ namespace MWMechanics
|
||||||
// if guard starts combat with player, guards pursuing player should do the same
|
// if guard starts combat with player, guards pursuing player should do the same
|
||||||
if (ptr.getClass().isClass(ptr, "Guard"))
|
if (ptr.getClass().isClass(ptr, "Guard"))
|
||||||
{
|
{
|
||||||
|
ptr.getClass().getCreatureStats(ptr).setHitAttemptActorId(target.getClass().getCreatureStats(target).getActorId()); // Stops guard from ending combat if player is unreachable
|
||||||
for (Actors::PtrActorMap::const_iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
|
for (Actors::PtrActorMap::const_iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
|
||||||
{
|
{
|
||||||
if (iter->first.getClass().isClass(iter->first, "Guard"))
|
if (iter->first.getClass().isClass(iter->first, "Guard"))
|
||||||
|
@ -1374,6 +1378,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
aiSeq.stopPursuit();
|
aiSeq.stopPursuit();
|
||||||
aiSeq.stack(MWMechanics::AiCombat(target), ptr);
|
aiSeq.stack(MWMechanics::AiCombat(target), ptr);
|
||||||
|
iter->first.getClass().getCreatureStats(iter->first).setHitAttemptActorId(target.getClass().getCreatureStats(target).getActorId()); // Stops guard from ending combat if player is unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,9 @@ void ESM::CreatureStats::load (ESMReader &esm)
|
||||||
mActorId = -1;
|
mActorId = -1;
|
||||||
esm.getHNOT (mActorId, "ACID");
|
esm.getHNOT (mActorId, "ACID");
|
||||||
|
|
||||||
|
mHitAttemptActorId = -1;
|
||||||
|
esm.getHNOT(mHitAttemptActorId, "HAID");
|
||||||
|
|
||||||
mDeathAnimation = -1;
|
mDeathAnimation = -1;
|
||||||
esm.getHNOT (mDeathAnimation, "DANM");
|
esm.getHNOT (mDeathAnimation, "DANM");
|
||||||
|
|
||||||
|
@ -203,6 +206,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
|
||||||
if (mActorId != -1)
|
if (mActorId != -1)
|
||||||
esm.writeHNT ("ACID", mActorId);
|
esm.writeHNT ("ACID", mActorId);
|
||||||
|
|
||||||
|
if (mHitAttemptActorId != -1)
|
||||||
|
esm.writeHNT("HAID", mHitAttemptActorId);
|
||||||
|
|
||||||
if (mDeathAnimation != -1)
|
if (mDeathAnimation != -1)
|
||||||
esm.writeHNT ("DANM", mDeathAnimation);
|
esm.writeHNT ("DANM", mDeathAnimation);
|
||||||
|
|
||||||
|
@ -240,6 +246,7 @@ void ESM::CreatureStats::blank()
|
||||||
mTradeTime.mDay = 0;
|
mTradeTime.mDay = 0;
|
||||||
mGoldPool = 0;
|
mGoldPool = 0;
|
||||||
mActorId = -1;
|
mActorId = -1;
|
||||||
|
mHitAttemptActorId = -1;
|
||||||
mHasAiSettings = false;
|
mHasAiSettings = false;
|
||||||
mDead = false;
|
mDead = false;
|
||||||
mDeathAnimationFinished = false;
|
mDeathAnimationFinished = false;
|
||||||
|
|
|
@ -38,6 +38,7 @@ namespace ESM
|
||||||
ESM::TimeStamp mTradeTime;
|
ESM::TimeStamp mTradeTime;
|
||||||
int mGoldPool;
|
int mGoldPool;
|
||||||
int mActorId;
|
int mActorId;
|
||||||
|
int mHitAttemptActorId;
|
||||||
|
|
||||||
bool mDead;
|
bool mDead;
|
||||||
bool mDeathAnimationFinished;
|
bool mDeathAnimationFinished;
|
||||||
|
|
|
@ -153,6 +153,10 @@ show effect duration = false
|
||||||
# Prevents merchants from equipping items that are sold to them.
|
# Prevents merchants from equipping items that are sold to them.
|
||||||
prevent merchant equipping = false
|
prevent merchant equipping = false
|
||||||
|
|
||||||
|
# Makes player followers and escorters start combat with enemies who have started combat with them
|
||||||
|
# or the player. Otherwise they wait for the enemies or the player to do an attack first.
|
||||||
|
followers attack on sight = false
|
||||||
|
|
||||||
[General]
|
[General]
|
||||||
|
|
||||||
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).
|
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).
|
||||||
|
|
Loading…
Reference in a new issue