mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-22 00:53:52 +00:00
Feature 1314: make npc fight creatures
This commit is contained in:
parent
725f6cac5e
commit
5be37f04ef
6 changed files with 187 additions and 62 deletions
|
@ -178,55 +178,66 @@ namespace MWMechanics
|
||||||
calculateDynamicStats (ptr);
|
calculateDynamicStats (ptr);
|
||||||
|
|
||||||
calculateCreatureStatModifiers (ptr, duration);
|
calculateCreatureStatModifiers (ptr, duration);
|
||||||
|
|
||||||
// AI
|
|
||||||
if(MWBase::Environment::get().getMechanicsManager()->isAIActive())
|
|
||||||
{
|
|
||||||
CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr);
|
|
||||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
|
||||||
|
|
||||||
//engage combat or not?
|
|
||||||
if(ptr != player && !creatureStats.isHostile())
|
|
||||||
{
|
|
||||||
ESM::Position playerpos = player.getRefData().getPosition();
|
|
||||||
ESM::Position actorpos = ptr.getRefData().getPosition();
|
|
||||||
float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0])
|
|
||||||
+(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1])
|
|
||||||
+(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2]));
|
|
||||||
float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified();
|
|
||||||
|
|
||||||
if( (fight == 100 )
|
|
||||||
|| (fight >= 95 && d <= 3000)
|
|
||||||
|| (fight >= 90 && d <= 2000)
|
|
||||||
|| (fight >= 80 && d <= 1000)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player)
|
|
||||||
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr);
|
|
||||||
|
|
||||||
if (LOS)
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateCrimePersuit(ptr, duration);
|
|
||||||
creatureStats.getAiSequence().execute (ptr,duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
// fatigue restoration
|
// fatigue restoration
|
||||||
calculateRestoration(ptr, duration, false);
|
calculateRestoration(ptr, duration, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused)
|
void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer)
|
||||||
{
|
{
|
||||||
if(!paused)
|
CreatureStats& creatureStats = MWWorld::Class::get(actor1).getCreatureStats(actor1);
|
||||||
|
|
||||||
|
if (againstPlayer && creatureStats.isHostile()) return; // already fighting against player
|
||||||
|
|
||||||
|
float fight;
|
||||||
|
|
||||||
|
if (againstPlayer)
|
||||||
|
fight = actor1.getClass().getCreatureStats(actor1).getAiSetting(CreatureStats::AI_Fight).getModified();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fight = 0;
|
||||||
|
// if one of actors is creature then we should make a decision to start combat or not
|
||||||
|
// NOTE: function doesn't take into account combat between 2 creatures
|
||||||
|
if (!actor1.getClass().isNpc())
|
||||||
|
{
|
||||||
|
// if creature is hostile then it is necessarily to start combat
|
||||||
|
if (creatureStats.isHostile()) fight = 100;
|
||||||
|
else fight = creatureStats.getAiSetting(CreatureStats::AI_Fight).getModified();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESM::Position actor1Pos = actor1.getRefData().getPosition();
|
||||||
|
ESM::Position actor2Pos = actor2.getRefData().getPosition();
|
||||||
|
float d = Ogre::Vector3(actor1Pos.pos).distance(Ogre::Vector3(actor2Pos.pos));
|
||||||
|
|
||||||
|
if( (fight == 100 && d <= 5000)
|
||||||
|
|| (fight >= 95 && d <= 3000)
|
||||||
|
|| (fight >= 90 && d <= 2000)
|
||||||
|
|| (fight >= 80 && d <= 1000))
|
||||||
|
{
|
||||||
|
if (againstPlayer || actor2.getClass().getCreatureStats(actor2).getAiSequence().canAddTarget(actor2Pos, d))
|
||||||
|
{
|
||||||
|
bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2);
|
||||||
|
|
||||||
|
if (againstPlayer) 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration)
|
||||||
{
|
{
|
||||||
updateDrowning(ptr, duration);
|
updateDrowning(ptr, duration);
|
||||||
calculateNpcStatModifiers(ptr);
|
calculateNpcStatModifiers(ptr);
|
||||||
updateEquippedLight(ptr, duration);
|
updateEquippedLight(ptr, duration);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void Actors::adjustMagicEffects (const MWWorld::Ptr& creature)
|
void Actors::adjustMagicEffects (const MWWorld::Ptr& creature)
|
||||||
{
|
{
|
||||||
|
@ -830,10 +841,24 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Ogre::Vector3 sBasePoint;
|
||||||
|
bool comparePtrDist (const MWWorld::Ptr& ptr1, const MWWorld::Ptr& ptr2)
|
||||||
|
{
|
||||||
|
return (sBasePoint.squaredDistance(Ogre::Vector3(ptr1.getRefData().getPosition().pos))
|
||||||
|
< sBasePoint.squaredDistance(Ogre::Vector3(ptr2.getRefData().getPosition().pos)));
|
||||||
|
}
|
||||||
|
|
||||||
void Actors::update (float duration, bool paused)
|
void Actors::update (float duration, bool paused)
|
||||||
{
|
{
|
||||||
if(!paused)
|
if(!paused)
|
||||||
{
|
{
|
||||||
|
std::list<MWWorld::Ptr> listGuards; // at the moment only guards certainly will fight with creatures
|
||||||
|
|
||||||
|
static float timerUpdateAITargets = 0;
|
||||||
|
|
||||||
|
// target lists get updated once every 1.0 sec
|
||||||
|
if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0;
|
||||||
|
|
||||||
// Reset data from previous frame
|
// Reset data from previous frame
|
||||||
for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||||
{
|
{
|
||||||
|
@ -841,19 +866,54 @@ namespace MWMechanics
|
||||||
// Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation
|
// Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation
|
||||||
// (below)
|
// (below)
|
||||||
iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string());
|
iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string());
|
||||||
|
|
||||||
|
// add guards to list to later make them fight with creatures
|
||||||
|
if (timerUpdateAITargets == 0 && iter->first.getClass().isClass(iter->first, "Guard"))
|
||||||
|
listGuards.push_back(iter->first);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||||
|
|
||||||
|
listGuards.push_back(player);
|
||||||
|
|
||||||
// AI and magic effects update
|
// AI and magic effects update
|
||||||
for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||||
{
|
{
|
||||||
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
|
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
|
||||||
{
|
{
|
||||||
updateActor(iter->first, duration);
|
updateActor(iter->first, duration);
|
||||||
|
|
||||||
|
if (MWBase::Environment::get().getMechanicsManager()->isAIActive())
|
||||||
|
{
|
||||||
|
// make guards and creatures fight each other
|
||||||
|
if (timerUpdateAITargets == 0 && !iter->first.getClass().isNpc() && !listGuards.empty())
|
||||||
|
{
|
||||||
|
//findNthClosest
|
||||||
|
sBasePoint = Ogre::Vector3(iter->first.getRefData().getPosition().pos);
|
||||||
|
listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest creature
|
||||||
|
|
||||||
|
for (std::list<MWWorld::Ptr>::const_iterator it = listGuards.cbegin(); it != listGuards.cend(); ++it)
|
||||||
|
{
|
||||||
|
engageCombat(iter->first, *it, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iter->first != player) engageCombat(iter->first, player, true);
|
||||||
|
|
||||||
|
if (iter->first.getClass().isNpc() && iter->first != player)
|
||||||
|
updateCrimePersuit(iter->first, duration);
|
||||||
|
|
||||||
|
if (iter->first != player)
|
||||||
|
iter->first.getClass().getCreatureStats(iter->first).getAiSequence().execute(iter->first, duration);
|
||||||
|
}
|
||||||
|
|
||||||
if(iter->first.getTypeName() == typeid(ESM::NPC).name())
|
if(iter->first.getTypeName() == typeid(ESM::NPC).name())
|
||||||
updateNpc(iter->first, duration, paused);
|
updateNpc(iter->first, duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timerUpdateAITargets += duration;
|
||||||
|
|
||||||
// Looping magic VFX update
|
// Looping magic VFX update
|
||||||
// Note: we need to do this before any of the animations are updated.
|
// Note: we need to do this before any of the animations are updated.
|
||||||
// Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
|
// Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
|
||||||
|
@ -872,7 +932,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill dead actors, update some variables
|
// Kill dead actors, update some variables
|
||||||
for (PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++)
|
for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||||
{
|
{
|
||||||
const MWWorld::Class &cls = MWWorld::Class::get(iter->first);
|
const MWWorld::Class &cls = MWWorld::Class::get(iter->first);
|
||||||
CreatureStats &stats = cls.getCreatureStats(iter->first);
|
CreatureStats &stats = cls.getCreatureStats(iter->first);
|
||||||
|
@ -945,7 +1005,6 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
// if player is in sneak state see if anyone detects him
|
// if player is in sneak state see if anyone detects him
|
||||||
const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
|
||||||
if (player.getClass().getCreatureStats(player).getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak))
|
if (player.getClass().getCreatureStats(player).getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak))
|
||||||
{
|
{
|
||||||
const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
|
const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
@ -1075,9 +1134,7 @@ namespace MWMechanics
|
||||||
if(!stats.isDead() && stats.getAiSequence().getTypeId() == AiPackage::TypeIdCombat)
|
if(!stats.isDead() && stats.getAiSequence().getTypeId() == AiPackage::TypeIdCombat)
|
||||||
{
|
{
|
||||||
MWMechanics::AiCombat* package = static_cast<MWMechanics::AiCombat*>(stats.getAiSequence().getActivePackage());
|
MWMechanics::AiCombat* package = static_cast<MWMechanics::AiCombat*>(stats.getAiSequence().getActivePackage());
|
||||||
// TODO: This is wrong! It's comparing Ref IDs with Ogre handles. The only case where this (coincidentally) works is the player.
|
if(package->getTarget() == actor)
|
||||||
// possibly applies to other code using getTargetId.
|
|
||||||
if(package->getTargetId() == actor.getCellRef().mRefID)
|
|
||||||
list.push_front(*iter);
|
list.push_front(*iter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
std::map<std::string, int> mDeathCount;
|
std::map<std::string, int> mDeathCount;
|
||||||
|
|
||||||
void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused);
|
void updateNpc(const MWWorld::Ptr &ptr, float duration);
|
||||||
|
|
||||||
void adjustMagicEffects (const MWWorld::Ptr& creature);
|
void adjustMagicEffects (const MWWorld::Ptr& creature);
|
||||||
|
|
||||||
|
@ -81,6 +81,12 @@ namespace MWMechanics
|
||||||
///< This function is normally called automatically during the update process, but it can
|
///< This function is normally called automatically during the update process, but it can
|
||||||
/// also be called explicitly at any time to force an update.
|
/// also be called explicitly at any time to force an update.
|
||||||
|
|
||||||
|
/** Start combat between two actors
|
||||||
|
@Notes: If againstPlayer = true then actor2 should be the Player.
|
||||||
|
If one of the combatants is creature it should be actor1.
|
||||||
|
*/
|
||||||
|
void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer);
|
||||||
|
|
||||||
void restoreDynamicStats(bool sleep);
|
void restoreDynamicStats(bool sleep);
|
||||||
///< If the player is sleeping, this should be called every hour.
|
///< If the player is sleeping, this should be called every hour.
|
||||||
|
|
||||||
|
|
|
@ -149,11 +149,8 @@ namespace MWMechanics
|
||||||
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
|
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
|
||||||
{
|
{
|
||||||
//General description
|
//General description
|
||||||
if(!actor.getClass().getCreatureStats(actor).isHostile()
|
if(actor.getClass().getCreatureStats(actor).isDead()
|
||||||
|| actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0)
|
|| mTarget.getClass().getCreatureStats(mTarget).isDead() )
|
||||||
return true;
|
|
||||||
|
|
||||||
if(mTarget.getClass().getCreatureStats(mTarget).isDead())
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
//Update every frame
|
//Update every frame
|
||||||
|
@ -627,9 +624,9 @@ namespace MWMechanics
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &AiCombat::getTargetId() const
|
const MWWorld::Ptr &AiCombat::getTarget() const
|
||||||
{
|
{
|
||||||
return mTarget.getRefData().getHandle();
|
return mTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
virtual unsigned int getPriority() const;
|
virtual unsigned int getPriority() const;
|
||||||
|
|
||||||
const std::string &getTargetId() const;
|
const MWWorld::Ptr &getTarget() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PathFinder mPathFinder;
|
PathFinder mPathFinder;
|
||||||
|
|
|
@ -61,7 +61,35 @@ bool MWMechanics::AiSequence::getCombatTarget(std::string &targetActorId) const
|
||||||
if (getTypeId() != AiPackage::TypeIdCombat)
|
if (getTypeId() != AiPackage::TypeIdCombat)
|
||||||
return false;
|
return false;
|
||||||
const AiCombat *combat = static_cast<const AiCombat *>(mPackages.front());
|
const AiCombat *combat = static_cast<const AiCombat *>(mPackages.front());
|
||||||
targetActorId = combat->getTargetId();
|
targetActorId = combat->getTarget().getRefData().getHandle();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MWMechanics::AiSequence::canAddTarget(const ESM::Position& actorPos, float distToTarget) const
|
||||||
|
{
|
||||||
|
bool firstCombatFound = false;
|
||||||
|
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||||
|
|
||||||
|
for(std::list<AiPackage*>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||||
|
{
|
||||||
|
if ((*it)->getTypeId() == AiPackage::TypeIdCombat)
|
||||||
|
{
|
||||||
|
firstCombatFound = true;
|
||||||
|
|
||||||
|
const AiCombat *combat = static_cast<const AiCombat *>(*it);
|
||||||
|
if (combat->getTarget() != player) return false; // only 1 non-player target allowed
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// add new target only if current target (player) is farther
|
||||||
|
ESM::Position &targetPos = combat->getTarget().getRefData().getPosition();
|
||||||
|
|
||||||
|
float distToCurrTarget = (Ogre::Vector3(targetPos.pos) - Ogre::Vector3(actorPos.pos)).length();
|
||||||
|
return (distToCurrTarget > distToTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (firstCombatFound) break; // assumes combat packages go one-by-one in packages list
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +124,40 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration)
|
||||||
{
|
{
|
||||||
MWMechanics::AiPackage* package = mPackages.front();
|
MWMechanics::AiPackage* package = mPackages.front();
|
||||||
mLastAiPackage = package->getTypeId();
|
mLastAiPackage = package->getTypeId();
|
||||||
|
|
||||||
|
// if active package is combat one, choose nearest target
|
||||||
|
if (mLastAiPackage == AiPackage::TypeIdCombat)
|
||||||
|
{
|
||||||
|
std::list<AiPackage *>::const_iterator itActualCombat;
|
||||||
|
|
||||||
|
float nearestDist = std::numeric_limits<float>::max();
|
||||||
|
Ogre::Vector3 vActorPos = Ogre::Vector3(actor.getRefData().getPosition().pos);
|
||||||
|
|
||||||
|
const AiCombat *package;
|
||||||
|
|
||||||
|
for(std::list<AiPackage *>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||||
|
{
|
||||||
|
package = static_cast<const AiCombat *>(*it);
|
||||||
|
|
||||||
|
if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break;
|
||||||
|
|
||||||
|
ESM::Position &targetPos = package->getTarget().getRefData().getPosition();
|
||||||
|
|
||||||
|
float distTo = (Ogre::Vector3(targetPos.pos) - vActorPos).length();
|
||||||
|
if (distTo < nearestDist)
|
||||||
|
{
|
||||||
|
nearestDist = distTo;
|
||||||
|
itActualCombat = it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPackages.cbegin() != itActualCombat)
|
||||||
|
{
|
||||||
|
// move combat package with nearest target to the front
|
||||||
|
mPackages.splice(mPackages.begin(), mPackages, itActualCombat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (package->execute (actor,duration))
|
if (package->execute (actor,duration))
|
||||||
{
|
{
|
||||||
// To account for the rare case where AiPackage::execute() queued another AI package
|
// To account for the rare case where AiPackage::execute() queued another AI package
|
||||||
|
|
|
@ -47,6 +47,9 @@ namespace MWMechanics
|
||||||
///< Return true and assign target if combat package is currently
|
///< Return true and assign target if combat package is currently
|
||||||
/// active, return false otherwise
|
/// active, return false otherwise
|
||||||
|
|
||||||
|
bool canAddTarget(const ESM::Position& actorPos, float distToTarget) const;
|
||||||
|
///< Function assumes that actor can have only 1 target apart player
|
||||||
|
|
||||||
void stopCombat();
|
void stopCombat();
|
||||||
///< Removes all combat packages until first non-combat or stack empty.
|
///< Removes all combat packages until first non-combat or stack empty.
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue