Implement awareness check function. Use this for combat AI and GetDetected instruction.

This commit is contained in:
scrawl 2014-01-07 00:51:09 +01:00
parent 4a3d148a48
commit 29c823b9d4
6 changed files with 121 additions and 51 deletions

View file

@ -88,6 +88,9 @@ namespace MWBase
virtual int countDeaths (const std::string& id) const = 0; virtual int countDeaths (const std::string& id) const = 0;
///< Return the number of deaths for actors with the given ID. ///< Return the number of deaths for actors with the given ID.
/// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check!
virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0;
enum PersuasionType enum PersuasionType
{ {
PT_Admire, PT_Admire,

View file

@ -158,50 +158,49 @@ namespace MWMechanics
calculateDynamicStats (ptr); calculateDynamicStats (ptr);
calculateCreatureStatModifiers (ptr, duration); calculateCreatureStatModifiers (ptr, duration);
if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) // AI
if(MWBase::Environment::get().getMechanicsManager()->isAIActive())
{ {
// AI CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
if(MWBase::Environment::get().getMechanicsManager()->isAIActive()) //engage combat or not?
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
if(ptr != player && !creatureStats.isHostile())
{ {
CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); ESM::Position playerpos = player.getRefData().getPosition();
//engage combat or not? ESM::Position actorpos = ptr.getRefData().getPosition();
if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile()) 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();
float disp = 100; //creatures don't have disposition, so set it to 100 by default
if(ptr.getTypeName() == typeid(ESM::NPC).name())
{ {
ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr);
ESM::Position actorpos = ptr.getRefData().getPosition(); }
float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player)
+(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr);
+(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); if( ( (fight == 100 )
float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified(); || (fight >= 95 && d <= 3000)
float disp = 100; //creatures don't have disposition, so set it to 100 by default || (fight >= 90 && d <= 2000)
if(ptr.getTypeName() == typeid(ESM::NPC).name()) || (fight >= 80 && d <= 1000)
{ || (fight >= 80 && disp <= 40)
disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); || (fight >= 70 && disp <= 35 && d <= 1000)
} || (fight >= 60 && disp <= 30 && d <= 1000)
bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); || (fight >= 50 && disp == 0)
if( ( (fight == 100 ) || (fight >= 40 && disp <= 10 && d <= 500) )
|| (fight >= 95 && d <= 3000) && LOS
|| (fight >= 90 && d <= 2000) )
|| (fight >= 80 && d <= 1000) {
|| (fight >= 80 && disp <= 40) creatureStats.getAiSequence().stack(AiCombat("player"));
|| (fight >= 70 && disp <= 35 && d <= 1000) creatureStats.setHostile(true);
|| (fight >= 60 && disp <= 30 && d <= 1000)
|| (fight >= 50 && disp == 0)
|| (fight >= 40 && disp <= 10 && d <= 500) )
&& LOS
)
{
creatureStats.getAiSequence().stack(AiCombat("player"));
creatureStats.setHostile(true);
}
} }
creatureStats.getAiSequence().execute (ptr,duration);
} }
// fatigue restoration creatureStats.getAiSequence().execute (ptr,duration);
calculateRestoration(ptr, duration);
} }
// fatigue restoration
calculateRestoration(ptr, duration);
} }
void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused) void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused)

View file

@ -12,6 +12,8 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/player.hpp" #include "../mwworld/player.hpp"
#include <OgreSceneNode.h>
#include "spellcasting.hpp" #include "spellcasting.hpp"
namespace MWMechanics namespace MWMechanics
@ -725,4 +727,72 @@ namespace MWMechanics
{ {
return mAI; return mAI;
} }
bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer)
{
const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
float invisibility = stats.getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude;
if (invisibility > 0)
return false;
float sneakTerm = 0;
if (ptr.getClass().getStance(ptr, MWWorld::Class::Sneak))
{
static float fSneakSkillMult = store.find("fSneakSkillMult")->getFloat();
static float fSneakBootMult = store.find("fSneakBootMult")->getFloat();
float sneak = 0;
if (ptr.getClass().isNpc())
sneak = ptr.getClass().getNpcStats(ptr).getSkill(ESM::Skill::Sneak).getModified();
int agility = stats.getAttribute(ESM::Attribute::Agility).getModified();
int luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float bootWeight = 0;
if (ptr.getClass().isNpc())
{
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
MWWorld::ContainerStoreIterator it = inv.getSlot(MWWorld::InventoryStore::Slot_Boots);
if (it != inv.end())
bootWeight = it->getClass().getWeight(*it);
}
sneakTerm = fSneakSkillMult * sneak + 0.2 * agility + 0.1 * luck + bootWeight * fSneakBootMult;
}
static float fSneakDistBase = store.find("fSneakDistanceBase")->getFloat();
static float fSneakDistMult = store.find("fSneakDistanceMultiplier")->getFloat();
Ogre::Vector3 pos1 (ptr.getRefData().getPosition().pos);
Ogre::Vector3 pos2 (observer.getRefData().getPosition().pos);
float distTerm = fSneakDistBase + fSneakDistMult * pos1.distance(pos2);
float chameleon = stats.getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude;
float x = sneakTerm * distTerm * stats.getFatigueTerm() + chameleon + invisibility;
CreatureStats& observerStats = observer.getClass().getCreatureStats(observer);
int obsAgility = observerStats.getAttribute(ESM::Attribute::Agility).getModified();
int obsLuck = observerStats.getAttribute(ESM::Attribute::Luck).getModified();
float obsBlind = observerStats.getMagicEffects().get(ESM::MagicEffect::Blind).mMagnitude;
int obsSneak = 0;
if (observer.getClass().isNpc())
obsSneak = observer.getClass().getNpcStats(observer).getSkill(ESM::Skill::Sneak).getModified();
float obsTerm = obsSneak + 0.2 * obsAgility + 0.1 * obsLuck - obsBlind;
// is ptr behind the observer?
static float fSneakNoViewMult = store.find("fSneakNoViewMult")->getFloat();
static float fSneakViewMult = store.find("fSneakViewMult")->getFloat();
float y = 0;
Ogre::Vector3 vec = pos1 - pos2;
Ogre::Radian angle = observer.getRefData().getBaseNode()->getOrientation().yAxis().angleBetween(vec);
if (angle < Ogre::Degree(90))
y = obsTerm * observerStats.getFatigueTerm() * fSneakNoViewMult;
else
y = obsTerm * observerStats.getFatigueTerm() * fSneakViewMult;
float target = x - y;
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
return (roll >= target);
}
} }

View file

@ -98,6 +98,9 @@ namespace MWMechanics
void toLower(std::string npcFaction); void toLower(std::string npcFaction);
///< Perform a persuasion action on NPC ///< Perform a persuasion action on NPC
/// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check!
virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer);
virtual void forceStateUpdate(const MWWorld::Ptr &ptr); virtual void forceStateUpdate(const MWWorld::Ptr &ptr);
virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);

View file

@ -358,19 +358,21 @@ namespace MWScript
}; };
template<class R> template<class R>
class OpGetDetected : public Interpreter::Opcode1 class OpGetDetected : public Interpreter::Opcode0
{ {
public: public:
virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) virtual void execute (Interpreter::Runtime& runtime)
{ {
MWWorld::Ptr observer = R()(runtime);
std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop(); runtime.pop();
Interpreter::Type_Integer value = false; // TODO replace with implementation MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPtr(actorID, true);
std::cout << "AiGetDetected: " << actorID << ", " << value << std::endl; Interpreter::Type_Integer value =
MWBase::Environment::get().getWorld()->getLOS(observer, actor) &&
MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer);
runtime.push (value); runtime.push (value);
} }
@ -432,8 +434,8 @@ namespace MWScript
new OpGetAiPackageDone<ExplicitRef>); new OpGetAiPackageDone<ExplicitRef>);
interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackage, new OpGetCurrentAIPackage<ImplicitRef>); interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackage, new OpGetCurrentAIPackage<ImplicitRef>);
interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage<ExplicitRef>); interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage<ExplicitRef>);
interpreter.installSegment3 (Compiler::Ai::opcodeGetDetected, new OpGetDetected<ImplicitRef>); interpreter.installSegment5 (Compiler::Ai::opcodeGetDetected, new OpGetDetected<ImplicitRef>);
interpreter.installSegment3 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected<ExplicitRef>); interpreter.installSegment5 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected<ExplicitRef>);
interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight<ImplicitRef>); interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight<ImplicitRef>);
interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight<ExplicitRef>); interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight<ExplicitRef>);
interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI<ImplicitRef>); interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI<ImplicitRef>);

View file

@ -1840,13 +1840,6 @@ namespace MWWorld
bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc)
{ {
// This is a placeholder! Needs to go into an NPC awareness check function (see
// https://wiki.openmw.org/index.php?title=Research:NPC_AI_Behaviour#NPC_Awareness_Check )
if (targetNpc.getClass().getCreatureStats(targetNpc).getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude)
return false;
if (targetNpc.getClass().getCreatureStats(targetNpc).getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude > 100)
return false;
Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents(); Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents();
float* pos1 = npc.getRefData().getPosition().pos; float* pos1 = npc.getRefData().getPosition().pos;
Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents(); Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents();