mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-01 09:15:36 +00:00
Implement awareness check function. Use this for combat AI and GetDetected instruction.
This commit is contained in:
parent
4a3d148a48
commit
29c823b9d4
6 changed files with 121 additions and 51 deletions
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
|
||||
#include <OgreSceneNode.h>
|
||||
|
||||
#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<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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -358,19 +358,21 @@ namespace MWScript
|
|||
};
|
||||
|
||||
template<class R>
|
||||
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<ExplicitRef>);
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackage, new OpGetCurrentAIPackage<ImplicitRef>);
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage<ExplicitRef>);
|
||||
interpreter.installSegment3 (Compiler::Ai::opcodeGetDetected, new OpGetDetected<ImplicitRef>);
|
||||
interpreter.installSegment3 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected<ExplicitRef>);
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetDetected, new OpGetDetected<ImplicitRef>);
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected<ExplicitRef>);
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight<ImplicitRef>);
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight<ExplicitRef>);
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI<ImplicitRef>);
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue