Merge pull request #146 from OpenMW/master while resolving conflicts

# Conflicts:
#	apps/openmw/mwclass/npc.cpp
#	apps/openmw/mwmechanics/actors.cpp
coverity_scan^2
David Cernat 8 years ago
commit 6763718412

@ -300,9 +300,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi fi
# Bullet # Bullet
download "Bullet 2.83.7" \ download "Bullet 2.86" \
"http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.7-msvc${MSVC_YEAR}-win${BITS}.7z" \ "http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" \
"Bullet-2.83.7-msvc${MSVC_YEAR}-win${BITS}.7z" "Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z"
# FFmpeg # FFmpeg
download "FFmpeg 3.0.1" \ download "FFmpeg 3.0.1" \
@ -414,7 +414,7 @@ cd $DEPS
echo echo
# Bullet # Bullet
printf "Bullet 2.83.7... " printf "Bullet 2.86... "
{ {
cd $DEPS_INSTALL cd $DEPS_INSTALL
@ -422,8 +422,8 @@ printf "Bullet 2.83.7... "
printf -- "Exists. (No version checking) " printf -- "Exists. (No version checking) "
elif [ -z $SKIP_EXTRACT ]; then elif [ -z $SKIP_EXTRACT ]; then
rm -rf Bullet rm -rf Bullet
eval 7z x -y "${DEPS}/Bullet-2.83.7-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP eval 7z x -y "${DEPS}/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
mv "Bullet-2.83.7-msvc${MSVC_YEAR}-win${BITS}" Bullet mv "Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}" Bullet
fi fi
export BULLET_ROOT="$(real_pwd)/Bullet" export BULLET_ROOT="$(real_pwd)/Bullet"

@ -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);
} }
} }
} }

@ -675,22 +675,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())
{ {
@ -708,7 +724,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())
@ -727,13 +743,11 @@ 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();
mwmp::DedicatedPlayer *dedicatedPlayer = mwmp::Players::getPlayer(attacker); mwmp::DedicatedPlayer *dedicatedPlayer = mwmp::Players::getPlayer(attacker);
@ -743,11 +757,9 @@ namespace MWClass
_knockdown = dedicatedPlayer->attack.knockdown; _knockdown = dedicatedPlayer->attack.knockdown;
if ((!isDedicated && ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) || _knockdown) if ((!isDedicated && ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) || _knockdown)
{ 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)
{ {
@ -826,13 +838,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,11 +9,12 @@
#include <components/sceneutil/positionattitudetransform.hpp> #include <components/sceneutil/positionattitudetransform.hpp>
#include <components/settings/settings.hpp>
#include "../mwmp/Main.hpp" #include "../mwmp/Main.hpp"
#include "../mwmp/DedicatedPlayer.hpp" #include "../mwmp/DedicatedPlayer.hpp"
#include "../mwmp/LocalPlayer.hpp" #include "../mwmp/LocalPlayer.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"
@ -285,7 +286,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;
@ -303,97 +304,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 // Stop here if target is unreachable
const std::list<MWWorld::Ptr>& followersAndEscorters = getActorsSidingWith(actor1); if (!MWMechanics::canFight(actor1, actor2))
for (std::list<MWWorld::Ptr>::const_iterator it = followersAndEscorters.begin(); it != followersAndEscorters.end(); ++it) return;
// If this is set to true, actor1 will start combat with actor2 if the awareness check at the end of the method returns true
bool aggressive = false;
// Get actors allied with actor1. Includes those following or escorting actor1, actors following or escorting those actors, (recursive)
// and any actor currently being followed or escorted by actor1
std::set<MWWorld::Ptr> allies1;
getActorsSidingWith(actor1, allies1);
// 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)
{ {
// Need to check both ways since player doesn't use AI packages if (creatureStats1.getAiSequence().isInCombat(*it))
if ((creatureStats2.getAiSequence().isInCombat(*it) continue;
|| it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(actor2))
&& !creatureStats1.getAiSequence().isInCombat(*it)) if (creatureStats2.matchesActorId(it->getClass().getCreatureStats(*it).getHitAttemptActorId()))
{ {
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
return; // 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;
} }
}
// Start combat if target actor is in combat with someone we are following through a follow package
for (std::list<MWMechanics::AiPackage*>::const_iterator it = creatureStats1.getAiSequence().begin(); it != creatureStats1.getAiSequence().end(); ++it)
{
if (!(*it)->sideWithTarget())
continue;
MWWorld::Ptr followTarget = (*it)->getTarget(); // 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;
}
if (followTarget.isEmpty()) std::set<MWWorld::Ptr> playerFollowersAndEscorters;
continue; getActorsSidingWith(MWMechanics::getPlayer(), playerFollowersAndEscorters);
if (creatureStats1.getAiSequence().isInCombat(followTarget)) bool isPlayerFollowerOrEscorter = std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor1) != playerFollowersAndEscorters.end();
continue;
// Need to check both ways since player doesn't use AI packages // If actor2 and at least one actor2 are in combat with actor1, actor1 and its allies start combat with them
if (creatureStats2.getAiSequence().isInCombat(followTarget) // Doesn't apply for player followers/escorters
|| followTarget.getClass().getCreatureStats(followTarget).getAiSequence().isInCombat(actor2)) if (!aggressive && !isPlayerFollowerOrEscorter)
{
// Check that actor2 is in combat with actor1
if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(actor1))
{ {
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); std::set<MWWorld::Ptr> allies2;
return; 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;
}
}
} }
} }
// Start combat with the player if we are already in combat with a player follower or escorter // If set in the settings file, player followers and escorters will become aggressive toward enemies in combat with them or the player
const std::list<MWWorld::Ptr>& playerFollowersAndEscorters = getActorsSidingWith(getPlayer()); if (!aggressive && isPlayerFollowerOrEscorter && Settings::Manager::getBool("followers attack on sight", "Game"))
if (againstPlayer)
{ {
for (std::list<MWWorld::Ptr>::const_iterator it = playerFollowersAndEscorters.begin(); it != playerFollowersAndEscorters.end(); ++it) if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(actor1))
aggressive = true;
else
{ {
if (creatureStats1.getAiSequence().isInCombat(*it)) for (std::set<MWWorld::Ptr>::const_iterator it = allies1.begin(); it != allies1.end(); ++it)
{ {
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(*it))
return; {
aggressive = true;
break;
}
} }
} }
} }
// Otherwise, don't initiate combat with an unreachable target // Do aggression check if actor2 is the player or a player follower or escorter
if (!MWMechanics::canFight(actor1,actor2)) if (!aggressive)
return;
bool aggressive = false;
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 if (againstPlayer || std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor2) != playerFollowersAndEscorters.end())
// other player followers or escorters {
if (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor1) != playerFollowersAndEscorters.end()) // Player followers and escorters with high fight should not initiate combat with the player or with
return; // other player followers or escorters
if (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor1) == playerFollowersAndEscorters.end())
aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2);
}
} }
else
// 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())
{ {
// Make guards fight aggressive creatures bool followerOrEscorter = false;
if (!actor1.getClass().isNpc() && actor2.getClass().isClass(actor2, "Guard")) for (std::list<MWMechanics::AiPackage*>::const_iterator it = creatureStats2.getAiSequence().begin(); it != creatureStats2.getAiSequence().end(); ++it)
{ {
if (creatureStats1.getAiSequence().isInCombat() && MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2)) // The follow package must be first or have nothing but combat before it
aggressive = true; 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);
LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1);
if (againstPlayer || std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor2) != playerFollowersAndEscorters.end())
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);
}
}
} }
} }
@ -940,7 +968,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);
@ -1074,10 +1105,25 @@ namespace MWMechanics
dedicatedPlayer->updateActor(iter->second); dedicatedPlayer->updateActor(iter->second);
// 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())
{ {
@ -1490,18 +1536,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;
} }
@ -1571,8 +1635,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;

@ -9,6 +9,7 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwrender/animation.hpp" #include "../mwrender/animation.hpp"
@ -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?

@ -1219,48 +1219,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;
@ -1370,6 +1373,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"))
@ -1379,6 +1383,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
} }
} }
} }

@ -35,6 +35,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape>
} }
else else
mShape.reset(new btBoxShape(toBullet(mHalfExtents))); mShape.reset(new btBoxShape(toBullet(mHalfExtents)));
mConvexShape = static_cast<btConvexShape*>(mShape.get());
mCollisionObject.reset(new btCollisionObject); mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);

@ -12,6 +12,7 @@
class btCollisionWorld; class btCollisionWorld;
class btCollisionShape; class btCollisionShape;
class btCollisionObject; class btCollisionObject;
class btConvexShape;
namespace Resource namespace Resource
{ {
@ -61,6 +62,8 @@ namespace MWPhysics
return mInternalCollisionMode; return mInternalCollisionMode;
} }
btConvexShape* getConvexShape() const { return mConvexShape; }
/** /**
* Enables or disables the *external* collision body. If disabled, other actors will not collide with this actor. * Enables or disables the *external* collision body. If disabled, other actors will not collide with this actor.
*/ */
@ -153,6 +156,7 @@ namespace MWPhysics
bool mWalkingOnWater; bool mWalkingOnWater;
std::auto_ptr<btCollisionShape> mShape; std::auto_ptr<btCollisionShape> mShape;
btConvexShape* mConvexShape;
std::auto_ptr<btCollisionObject> mCollisionObject; std::auto_ptr<btCollisionObject> mCollisionObject;

@ -237,9 +237,10 @@ namespace MWPhysics
public: public:
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight) static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, osg::Vec3f position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight)
{ {
osg::Vec3f position(ptr.getRefData().getPosition().asVec3()); osg::Vec3f offset = actor->getCollisionObjectPosition() - ptr.getRefData().getPosition().asVec3();
position += offset;
ActorTracer tracer; ActorTracer tracer;
tracer.findGround(actor, position, position-osg::Vec3f(0,0,maxHeight), collisionWorld); tracer.findGround(actor, position, position-osg::Vec3f(0,0,maxHeight), collisionWorld);
@ -401,7 +402,7 @@ namespace MWPhysics
if (tracer.mFraction < 1E-9f) if (tracer.mFraction < 1E-9f)
{ {
// Try to separate by backing off slighly to unstuck the solver // Try to separate by backing off slighly to unstuck the solver
const osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-3f; osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f;
newPosition += backOff; newPosition += backOff;
} }
@ -1085,13 +1086,13 @@ namespace MWPhysics
return resultCallback.mResult; return resultCallback.mResult;
} }
osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight) osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight)
{ {
ActorMap::iterator found = mActors.find(ptr); ActorMap::iterator found = mActors.find(ptr);
if (found == mActors.end()) if (found == mActors.end())
return ptr.getRefData().getPosition().asVec3(); return ptr.getRefData().getPosition().asVec3();
else else
return MovementSolver::traceDown(ptr, found->second, mCollisionWorld, maxHeight); return MovementSolver::traceDown(ptr, position, found->second, mCollisionWorld, maxHeight);
} }
void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts) void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts)

@ -90,7 +90,7 @@ namespace MWPhysics
void debugDraw(); void debugDraw();
std::vector<MWWorld::Ptr> getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with std::vector<MWWorld::Ptr> getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with
osg::Vec3f traceDown(const MWWorld::Ptr &ptr, float maxHeight); osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight);
std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::ConstPtr& actor, std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::ConstPtr& actor,
const osg::Vec3f &origin, const osg::Vec3f &origin,

@ -4,7 +4,6 @@
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h> #include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <BulletCollision/CollisionShapes/btConvexShape.h> #include <BulletCollision/CollisionShapes/btConvexShape.h>
#include <BulletCollision/CollisionShapes/btCylinderShape.h>
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "actor.hpp" #include "actor.hpp"
@ -106,12 +105,7 @@ void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const
newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask; newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask;
newTraceCallback.m_collisionFilterMask &= ~CollisionType_Actor; newTraceCallback.m_collisionFilterMask &= ~CollisionType_Actor;
btVector3 halfExtents = toBullet(actor->getHalfExtents()); world->convexSweepTest(actor->getConvexShape(), from, to, newTraceCallback);
halfExtents[2] = 1.0f;
btCylinderShapeZ base(halfExtents);
world->convexSweepTest(&base, from, to, newTraceCallback);
if(newTraceCallback.hasHit()) if(newTraceCallback.hasHit())
{ {
const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld;

@ -184,7 +184,7 @@ namespace MWRender
void CharacterPreview::redraw() void CharacterPreview::redraw()
{ {
mCamera->setNodeMask(~0); mCamera->setNodeMask(Mask_RenderToTexture);
mDrawOnceCallback->redrawNextFrame(); mDrawOnceCallback->redrawNextFrame();
} }

@ -1322,7 +1322,7 @@ namespace MWWorld
void World::adjustPosition(const Ptr &ptr, bool force) void World::adjustPosition(const Ptr &ptr, bool force)
{ {
ESM::Position pos (ptr.getRefData().getPosition()); osg::Vec3f pos (ptr.getRefData().getPosition().asVec3());
if(!ptr.getRefData().getBaseNode()) if(!ptr.getRefData().getBaseNode())
{ {
@ -1332,34 +1332,31 @@ namespace MWWorld
float terrainHeight = -std::numeric_limits<float>::max(); float terrainHeight = -std::numeric_limits<float>::max();
if (ptr.getCell()->isExterior()) if (ptr.getCell()->isExterior())
terrainHeight = getTerrainHeightAt(pos.asVec3()); terrainHeight = getTerrainHeightAt(pos);
if (pos.pos[2] < terrainHeight) if (pos.z() < terrainHeight)
pos.pos[2] = terrainHeight; pos.z() = terrainHeight;
pos.pos[2] += 20; // place slightly above. will snap down to ground with code below pos.z() += 20; // place slightly above. will snap down to ground with code below
ptr.getRefData().setPosition(pos);
if (force || !isFlying(ptr)) if (force || !isFlying(ptr))
{ {
osg::Vec3f traced = mPhysics->traceDown(ptr, 500); osg::Vec3f traced = mPhysics->traceDown(ptr, pos, 500);
if (traced.z() < pos.pos[2]) if (traced.z() < pos.z())
pos.pos[2] = traced.z(); pos.z() = traced.z();
} }
moveObject(ptr, ptr.getCell(), pos.pos[0], pos.pos[1], pos.pos[2]); moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z());
} }
void World::fixPosition(const Ptr &actor) void World::fixPosition(const Ptr &actor)
{ {
const float dist = 8000; const float dist = 8000;
ESM::Position pos (actor.getRefData().getPosition()); osg::Vec3f pos (actor.getRefData().getPosition().asVec3());
pos.pos[2] += dist; pos.z() += dist;
actor.getRefData().setPosition(pos);
osg::Vec3f traced = mPhysics->traceDown(actor, dist*1.1f); osg::Vec3f traced = mPhysics->traceDown(actor, pos, dist*1.1f);
if (traced != pos.asVec3()) if (traced != pos)
moveObject(actor, actor.getCell(), traced.x(), traced.y(), traced.z()); moveObject(actor, actor.getCell(), traced.x(), traced.y(), traced.z());
} }

@ -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…
Cancel
Save