From 29c823b9d435ff9b5cb8c2015f0230f209ad164e Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 7 Jan 2014 00:51:09 +0100 Subject: [PATCH] Implement awareness check function. Use this for combat AI and GetDetected instruction. --- apps/openmw/mwbase/mechanicsmanager.hpp | 3 + apps/openmw/mwmechanics/actors.cpp | 73 +++++++++---------- .../mwmechanics/mechanicsmanagerimp.cpp | 70 ++++++++++++++++++ .../mwmechanics/mechanicsmanagerimp.hpp | 3 + apps/openmw/mwscript/aiextensions.cpp | 16 ++-- apps/openmw/mwworld/worldimp.cpp | 7 -- 6 files changed, 121 insertions(+), 51 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 3ab234de1..90f0caee6 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -88,6 +88,9 @@ namespace MWBase virtual int countDeaths (const std::string& id) const = 0; ///< 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 { PT_Admire, diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 2cbfc020e..4c9c73ca1 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -158,50 +158,49 @@ namespace MWMechanics calculateDynamicStats (ptr); calculateCreatureStatModifiers (ptr, duration); - if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) + // AI + if(MWBase::Environment::get().getMechanicsManager()->isAIActive()) { - // AI - if(MWBase::Environment::get().getMechanicsManager()->isAIActive()) + CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + //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); - //engage combat or not? - if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !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(); + 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(); - 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(); - float disp = 100; //creatures don't have disposition, so set it to 100 by default - if(ptr.getTypeName() == typeid(ESM::NPC).name()) - { - disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); - } - bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - if( ( (fight == 100 ) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000) - || (fight >= 80 && disp <= 40) - || (fight >= 70 && disp <= 35 && d <= 1000) - || (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); - } + disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); + } + bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr); + if( ( (fight == 100 ) + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (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 - calculateRestoration(ptr, duration); + creatureStats.getAiSequence().execute (ptr,duration); } + + // fatigue restoration + calculateRestoration(ptr, duration); } void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 67f42fe0a..07d41505b 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -12,6 +12,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include + #include "spellcasting.hpp" namespace MWMechanics @@ -725,4 +727,72 @@ namespace MWMechanics { return mAI; } + + bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) + { + const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); + + 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 (RAND_MAX) + 1) * 100; // [0, 99] + + return (roll >= target); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index ec03b457b..63111f1cc 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -98,6 +98,9 @@ namespace MWMechanics void toLower(std::string npcFaction); ///< 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 playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 966a064c7..213f13882 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -358,19 +358,21 @@ namespace MWScript }; template - class OpGetDetected : public Interpreter::Opcode1 + class OpGetDetected : public Interpreter::Opcode0 { 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); 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); } @@ -432,8 +434,8 @@ namespace MWScript new OpGetAiPackageDone); interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackage, new OpGetCurrentAIPackage); interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage); - interpreter.installSegment3 (Compiler::Ai::opcodeGetDetected, new OpGetDetected); - interpreter.installSegment3 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected); + interpreter.installSegment5 (Compiler::Ai::opcodeGetDetected, new OpGetDetected); + interpreter.installSegment5 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected); interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight); interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight); interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ba76eee88..2d3d3d07f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1840,13 +1840,6 @@ namespace MWWorld 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(); float* pos1 = npc.getRefData().getPosition().pos; Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents();