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 7 years ago
commit 6763718412

@ -300,9 +300,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi
# Bullet
download "Bullet 2.83.7" \
"http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.7-msvc${MSVC_YEAR}-win${BITS}.7z" \
"Bullet-2.83.7-msvc${MSVC_YEAR}-win${BITS}.7z"
download "Bullet 2.86" \
"http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" \
"Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z"
# FFmpeg
download "FFmpeg 3.0.1" \
@ -414,7 +414,7 @@ cd $DEPS
echo
# Bullet
printf "Bullet 2.83.7... "
printf "Bullet 2.86... "
{
cd $DEPS_INSTALL
@ -422,8 +422,8 @@ printf "Bullet 2.83.7... "
printf -- "Exists. (No version checking) "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf Bullet
eval 7z x -y "${DEPS}/Bullet-2.83.7-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
mv "Bullet-2.83.7-msvc${MSVC_YEAR}-win${BITS}" Bullet
eval 7z x -y "${DEPS}/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
mv "Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}" Bullet
fi
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
{
// 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))
getCreatureStats(ptr).setAttacked(true);
// NOTE: 'object' and/or 'attacker' may be empty.
if (!attacker.isEmpty() && !stats.getAiSequence().isInCombat(attacker))
stats.setAttacked(true);
// Self defense
bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits.
// No retaliation for totally static creatures (they have no movement or attacks anyway)
if (isMobile(ptr) && !attacker.isEmpty())
{
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())
getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId());
stats.setLastHitAttemptObject(object.getCellRef().getRefId());
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
{
@ -358,7 +374,7 @@ namespace MWClass
}
if (!object.isEmpty())
getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId());
stats.setLastHitObject(object.getCellRef().getRefId());
if (damage > 0.0f && !object.isEmpty())
MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
@ -371,16 +387,13 @@ namespace MWClass
if (!attacker.isEmpty())
{
// Check for knockdown
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat();
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat();
float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
* getGmst().iKnockDownOddsMult->getInt() * 0.01f + getGmst().iKnockDownOddsBase->getInt();
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
{
getCreatureStats(ptr).setKnockedDown(true);
}
stats.setKnockedDown(true);
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);
@ -395,15 +408,15 @@ namespace MWClass
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);
getCreatureStats(ptr).setHealth(health);
stats.setHealth(health);
}
else
{
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
MWMechanics::DynamicStat<float> fatigue(stats.getFatigue());
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
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
// NOTE: 'object' and/or 'attacker' may be empty.
bool wasDead = getCreatureStats(ptr).isDead();
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
bool wasDead = stats.isDead();
// Note OnPcHitMe is not set for friendly hits.
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);
}
// 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())
getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId());
stats.setLastHitAttemptObject(object.getCellRef().getRefId());
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
{
@ -708,7 +724,7 @@ namespace MWClass
}
if (!object.isEmpty())
getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId());
stats.setLastHitObject(object.getCellRef().getRefId());
if (damage > 0.0f && !object.isEmpty())
@ -727,13 +743,11 @@ namespace MWClass
int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->getInt();
if (Misc::Rng::roll0to99() < chance)
{
MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
}
// Check for knockdown
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat();
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat();
float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
* gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt();
mwmp::DedicatedPlayer *dedicatedPlayer = mwmp::Players::getPlayer(attacker);
@ -743,11 +757,9 @@ namespace MWClass
_knockdown = dedicatedPlayer->attack.knockdown;
if ((!isDedicated && ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) || _knockdown)
{
getCreatureStats(ptr).setKnockedDown(true);
}
stats.setKnockedDown(true);
else
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
stats.setHitRecovery(true); // Is this supposed to always occur?
if (damage > 0 && ishealth)
{
@ -826,13 +838,13 @@ namespace MWClass
}
MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
health.setCurrent(health.getCurrent() - damage);
getCreatureStats(ptr).setHealth(health);
stats.setHealth(health);
}
else
{
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
getCreatureStats(ptr).setFatigue(fatigue);
stats.setFatigue(fatigue);
}
if (!wasDead && getCreatureStats(ptr).isDead())

@ -9,11 +9,12 @@
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/settings/settings.hpp>
#include "../mwmp/Main.hpp"
#include "../mwmp/DedicatedPlayer.hpp"
#include "../mwmp/LocalPlayer.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
@ -285,7 +286,7 @@ namespace MWMechanics
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))
return;
@ -303,97 +304,124 @@ namespace MWMechanics
if (!actor1.getClass().isMobile(actor1))
return;
// Start combat if target actor is in combat with one of our followers or escorters
const std::list<MWWorld::Ptr>& followersAndEscorters = getActorsSidingWith(actor1);
for (std::list<MWWorld::Ptr>::const_iterator it = followersAndEscorters.begin(); it != followersAndEscorters.end(); ++it)
// Stop here if target is unreachable
if (!MWMechanics::canFight(actor1, actor2))
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 ((creatureStats2.getAiSequence().isInCombat(*it)
|| it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(actor2))
&& !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);
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())
continue;
std::set<MWWorld::Ptr> playerFollowersAndEscorters;
getActorsSidingWith(MWMechanics::getPlayer(), playerFollowersAndEscorters);
if (creatureStats1.getAiSequence().isInCombat(followTarget))
continue;
bool isPlayerFollowerOrEscorter = std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor1) != playerFollowersAndEscorters.end();
// Need to check both ways since player doesn't use AI packages
if (creatureStats2.getAiSequence().isInCombat(followTarget)
|| followTarget.getClass().getCreatureStats(followTarget).getAiSequence().isInCombat(actor2))
// 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))
{
MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
return;
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;
}
}
}
}
// 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)
// 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"))
{
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);
return;
if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(*it))
{
aggressive = true;
break;
}
}
}
}
// Otherwise, don't initiate combat with an unreachable target
if (!MWMechanics::canFight(actor1,actor2))
return;
bool aggressive = false;
if (againstPlayer || std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor2) != playerFollowersAndEscorters.end())
// Do aggression check if actor2 is the player or a player follower or escorter
if (!aggressive)
{
// Player followers and escorters with high fight should not initiate combat here with the player or with
// other player followers or escorters
if (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor1) != playerFollowersAndEscorters.end())
return;
aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2);
if (againstPlayer || std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor2) != playerFollowersAndEscorters.end())
{
// Player followers and escorters with high fight should not initiate combat with the player or with
// other player followers or escorters
if (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), actor1) == playerFollowersAndEscorters.end())
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
if (!actor1.getClass().isNpc() && actor2.getClass().isClass(actor2, "Guard"))
bool followerOrEscorter = false;
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))
aggressive = true;
// 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)
{
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)
{
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();
if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier)
{
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
creatureStats.getAiSequence().stack(AiPursue(player), ptr);
creatureStats.setAlarmed(true);
@ -1074,10 +1105,25 @@ namespace MWMechanics
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())
{
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();
MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports
updateActor(actor, duration);
if (!cellChanged && MWBase::Environment::get().getWorld()->hasCellChanged())
{
@ -1490,18 +1536,36 @@ namespace MWMechanics
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
{
const MWWorld::Class &cls = iter->first.getClass();
CreatureStats &stats = cls.getCreatureStats(iter->first);
const CreatureStats &stats = cls.getCreatureStats(iter->first);
if (stats.isDead())
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)
{
if ((*it)->sideWithTarget() && (*it)->getTarget() == actor)
{
list.push_back(iter->first);
break;
}
else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat)
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;
}
@ -1571,8 +1635,6 @@ namespace MWMechanics
return list;
}
std::list<MWWorld::Ptr> Actors::getActorsFighting(const MWWorld::Ptr& actor) {
std::list<MWWorld::Ptr> list;
std::vector<MWWorld::Ptr> neighbors;

@ -9,6 +9,7 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwrender/animation.hpp"
@ -233,9 +234,14 @@ namespace MWMechanics
storage.stopAttack();
characterController.setAttackingOrSpell(false);
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;
else
else // Otherwise end combat
return true;
}

@ -21,7 +21,7 @@ namespace MWMechanics
mTalkedTo (false), mAlarmed (false), mAttacked (false),
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
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)
{
for (int i=0; i<4; ++i)
@ -371,6 +371,16 @@ namespace MWMechanics
return mLastHitAttemptObject;
}
void CreatureStats::setHitAttemptActorId(int actorId)
{
mHitAttemptActorId = actorId;
}
int CreatureStats::getHitAttemptActorId() const
{
return mHitAttemptActorId;
}
void CreatureStats::addToFallHeight(float height)
{
mFallHeight += height;
@ -521,6 +531,7 @@ namespace MWMechanics
state.mActorId = mActorId;
state.mDeathAnimation = mDeathAnimation;
state.mTimeOfDeath = mTimeOfDeath.toEsm();
//state.mHitAttemptActorId = mHitAttemptActorId;
mSpells.writeState(state.mSpells);
mActiveSpells.writeState(state.mActiveSpells);
@ -569,6 +580,7 @@ namespace MWMechanics
mActorId = state.mActorId;
mDeathAnimation = state.mDeathAnimation;
mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath);
//mHitAttemptActorId = state.mHitAttemptActorId;
mSpells.readState(state.mSpells);
mActiveSpells.readState(state.mActiveSpells);

@ -62,6 +62,8 @@ namespace MWMechanics
int mGoldPool;
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
signed char mDeathAnimation;
@ -241,9 +243,11 @@ namespace MWMechanics
bool getStance (Stance flag) const;
void setLastHitObject(const std::string &objectid);
void setLastHitAttemptObject(const std::string &objectid);
const std::string &getLastHitObject() const;
void setLastHitAttemptObject(const std::string &objectid);
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.
// 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;
std::list<MWWorld::Ptr> followers = getActorsSidingWith(attacker);
MWMechanics::CreatureStats& targetStats = ptr.getClass().getCreatureStats(ptr);
if (std::find(followers.begin(), followers.end(), ptr) != followers.end())
if (std::find(followersAttacker.begin(), followersAttacker.end(), target) != followersAttacker.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;
}
}
// 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;
for (std::list<AiPackage*>::const_iterator it = seq.begin(); it != seq.end(); ++it)
{
if ((*it)->getTypeId() == AiPackage::TypeIdCombat)
{
MWWorld::Ptr target = (*it)->getTarget();
if (!target.isEmpty() && target.getClass().isNpc())
MWWorld::Ptr target2 = (*it)->getTarget();
if (!target2.isEmpty() && target2.getClass().isNpc())
isFightingNpc = true;
}
}
if (ptr.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker)
&& !isAggressive(ptr, attacker) && !isFightingNpc)
commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault);
if (target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker)
&& !isAggressive(target, attacker) && !isFightingNpc)
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())
&& !seq.isInCombat(attacker))
{
// 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.
startCombat(ptr, attacker);
startCombat(target, attacker);
}
return true;
@ -1370,6 +1373,7 @@ namespace MWMechanics
// if guard starts combat with player, guards pursuing player should do the same
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)
{
if (iter->first.getClass().isClass(iter->first, "Guard"))
@ -1379,6 +1383,7 @@ namespace MWMechanics
{
aiSeq.stopPursuit();
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
mShape.reset(new btBoxShape(toBullet(mHalfExtents)));
mConvexShape = static_cast<btConvexShape*>(mShape.get());
mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);

@ -12,6 +12,7 @@
class btCollisionWorld;
class btCollisionShape;
class btCollisionObject;
class btConvexShape;
namespace Resource
{
@ -61,6 +62,8 @@ namespace MWPhysics
return mInternalCollisionMode;
}
btConvexShape* getConvexShape() const { return mConvexShape; }
/**
* 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;
std::auto_ptr<btCollisionShape> mShape;
btConvexShape* mConvexShape;
std::auto_ptr<btCollisionObject> mCollisionObject;

@ -237,9 +237,10 @@ namespace MWPhysics
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;
tracer.findGround(actor, position, position-osg::Vec3f(0,0,maxHeight), collisionWorld);
@ -401,7 +402,7 @@ namespace MWPhysics
if (tracer.mFraction < 1E-9f)
{
// 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;
}
@ -1085,13 +1086,13 @@ namespace MWPhysics
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);
if (found == mActors.end())
return ptr.getRefData().getPosition().asVec3();
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)

@ -90,7 +90,7 @@ namespace MWPhysics
void debugDraw();
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,
const osg::Vec3f &origin,

@ -4,7 +4,6 @@
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <BulletCollision/CollisionShapes/btConvexShape.h>
#include <BulletCollision/CollisionShapes/btCylinderShape.h>
#include "collisiontype.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 &= ~CollisionType_Actor;
btVector3 halfExtents = toBullet(actor->getHalfExtents());
halfExtents[2] = 1.0f;
btCylinderShapeZ base(halfExtents);
world->convexSweepTest(&base, from, to, newTraceCallback);
world->convexSweepTest(actor->getConvexShape(), from, to, newTraceCallback);
if(newTraceCallback.hasHit())
{
const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld;

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

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

@ -90,6 +90,9 @@ void ESM::CreatureStats::load (ESMReader &esm)
mActorId = -1;
esm.getHNOT (mActorId, "ACID");
//mHitAttemptActorId = -1;
//esm.getHNOT(mHitAttemptActorId, "HAID");
mDeathAnimation = -1;
esm.getHNOT (mDeathAnimation, "DANM");
@ -203,6 +206,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
if (mActorId != -1)
esm.writeHNT ("ACID", mActorId);
//if (mHitAttemptActorId != -1)
// esm.writeHNT("HAID", mHitAttemptActorId);
if (mDeathAnimation != -1)
esm.writeHNT ("DANM", mDeathAnimation);
@ -240,6 +246,7 @@ void ESM::CreatureStats::blank()
mTradeTime.mDay = 0;
mGoldPool = 0;
mActorId = -1;
//mHitAttemptActorId = -1;
mHasAiSettings = false;
mDead = false;
mDeathAnimationFinished = false;

@ -38,6 +38,7 @@ namespace ESM
ESM::TimeStamp mTradeTime;
int mGoldPool;
int mActorId;
//int mHitAttemptActorId;
bool mDead;
bool mDeathAnimationFinished;

@ -153,6 +153,10 @@ show effect duration = false
# Prevents merchants from equipping items that are sold to them.
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]
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).

Loading…
Cancel
Save