From 4037f3705e8855933a04a76b1bc717840ac99fd7 Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Tue, 1 Apr 2014 14:15:55 -0400 Subject: [PATCH] Feature 1154 & 73: NPCs react to crime --- apps/openmw/mwclass/npc.cpp | 5 + apps/openmw/mwclass/npc.hpp | 2 + apps/openmw/mwmechanics/aiactivate.cpp | 9 +- apps/openmw/mwmechanics/aiactivate.hpp | 25 +-- apps/openmw/mwmechanics/aisequence.cpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 150 ++++++++++++------ apps/openmw/mwscript/aiextensions.cpp | 2 +- apps/openmw/mwworld/class.cpp | 5 + apps/openmw/mwworld/class.hpp | 2 + 9 files changed, 140 insertions(+), 62 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d41f7002a..4b84bd03e 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1297,6 +1297,11 @@ namespace MWClass return ref->mBase->mNpdt12.mGold; } + bool Npc::isClass(const MWWorld::Ptr& ptr, const std::string &className) const + { + return ptr.get()->mBase->mClass == className; + } + const ESM::GameSetting *Npc::fMinWalkSpeed; const ESM::GameSetting *Npc::fMaxWalkSpeed; const ESM::GameSetting *Npc::fEncumberedMoveEffect; diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index fb45a2f1f..596bf0e56 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -168,6 +168,8 @@ namespace MWClass ///< Write additional state from \a ptr into \a state. virtual int getBaseGold(const MWWorld::Ptr& ptr) const; + + virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; }; } diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 8610cf4b2..b3fe40e1f 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -10,8 +10,9 @@ #include "steering.hpp" #include "movement.hpp" -MWMechanics::AiActivate::AiActivate(const std::string &objectId) - : mObjectId(objectId) +MWMechanics::AiActivate::AiActivate(const std::string &objectId, int arg) + : mObjectId(objectId), + mArg(arg) { } MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const @@ -25,6 +26,10 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) Movement &movement = actor.getClass().getMovementSettings(actor); const ESM::Cell *cell = actor.getCell()->getCell(); + // Make guard chase player + //if (mArg == 1) + // actor.getClass().getNpcStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + MWWorld::Ptr player = world->getPlayerPtr(); if(cell->mData.mX != player.getCell()->getCell()->mData.mX) { diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 7c94c2589..aa8725db2 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -1,25 +1,26 @@ #ifndef GAME_MWMECHANICS_AIACTIVATE_H #define GAME_MWMECHANICS_AIACTIVATE_H -#include "aipackage.hpp" -#include - -#include "pathfinding.hpp" - -namespace MWMechanics -{ +#include "aipackage.hpp" +#include + +#include "pathfinding.hpp" + +namespace MWMechanics +{ class AiActivate : public AiPackage { public: - AiActivate(const std::string &objectId); - virtual AiActivate *clone() const; - virtual bool execute (const MWWorld::Ptr& actor,float duration); - ///< \return Package completed? + AiActivate(const std::string &objectId, int arg); + virtual AiActivate *clone() const; + virtual bool execute (const MWWorld::Ptr& actor,float duration); + ///< \return Package completed? virtual int getTypeId() const; - private: + private: std::string mObjectId; + int mArg; PathFinder mPathFinder; int mCellX; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 2110393fd..ba69b8098 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -161,7 +161,7 @@ void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list) else if (it->mType == ESM::AI_Activate) { ESM::AIActivate data = it->mActivate; - package = new MWMechanics::AiActivate(data.mName.toString()); + package = new MWMechanics::AiActivate(data.mName.toString(), 0); } else //if (it->mType == ESM::AI_Follow) { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 4c8f35edb..b7ff10381 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -12,6 +12,9 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "aicombat.hpp" +#include "aiactivate.hpp" + #include #include "spellcasting.hpp" @@ -801,42 +804,121 @@ namespace MWMechanics bool MechanicsManager::commitCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) { - if (ptr.getRefData().getHandle() != "player") + // NOTE: int arg can be from itemTaken() so DON'T modify it, since it is + // passed to reportCrime later on in this function. + + // Only player can commit crime and no victimless crimes + if (ptr.getRefData().getHandle() != "player" || victim.isEmpty()) return false; - bool reported=false; - for (Actors::PtrControllerMap::const_iterator it = mActors.begin(); it != mActors.end(); ++it) - { - if (it->first != ptr && - MWBase::Environment::get().getWorld()->getLOS(ptr, it->first) && - awarenessCheck(ptr, it->first)) - { - // NPCs will always curse you when they notice you steal their items, even if they don't report the crime - if (it->first == victim && type == OT_Theft) - { - MWBase::Environment::get().getDialogueManager()->say(victim, "Thief"); - } + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - // Actor has witnessed a crime. Will he report it? - // (not sure, is > 0 correct?) - if (it->first.getClass().getCreatureStats(it->first).getAiSetting(CreatureStats::AI_Alarm).getModified() > 0) + // What amount of alarm did this crime generate? + int alarm; + if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) + alarm = esmStore.get().find("iAlarmTresspass")->getInt(); + else if (type == OT_Pickpocket) + alarm = esmStore.get().find("iAlarmPickPocket")->getInt(); + else if (type == OT_Assault) + alarm = esmStore.get().find("iAlarmAttack")->getInt(); + else if (type == OT_Murder) + alarm = esmStore.get().find("iAlarmKilling")->getInt(); + else if (type == OT_Theft) + alarm = esmStore.get().find("iAlarmStealing")->getInt(); + + // Innocent until proven guilty + bool reported = false; + + // Find all the NPC's close enough, ie. within fAlarmRadius of the player + std::vector neighbors; + mActors.getObjectsInRange(Ogre::Vector3(ptr.getRefData().getPosition().pos), + esmStore.get().find("fAlarmRadius")->getInt(), neighbors); + + // Did anyone see the crime? + for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) + { + if (*it == ptr) // Not the player + continue; + + CreatureStats& creatureStats = MWWorld::Class::get(*it).getCreatureStats(*it); + + // Did the witness see the crime? + if ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) + { + // Say something! + // TODO: Add more messages + if (type == OT_Theft) + MWBase::Environment::get().getDialogueManager()->say(*it, "Thief"); + + // Will the witness report the crime? + if (creatureStats.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { - // TODO: stats.setAlarmed(true) on NPCs within earshot - // fAlarmRadius ? - reported=true; - break; + creatureStats.setAlarmed(true); + reported = true; + reportCrime(ptr, victim, type, arg); + + // Is it a guard? Or will the witness fight? + if (it->getClass().isClass(*it, "Guard")) + { + // TODO: Persue player, concider bounty? + creatureStats.getAiSequence().stack(AiActivate(ptr.getClass().getId(ptr), 1)); + creatureStats.getAiSequence().execute(*it,0); + } + else if (creatureStats.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) + { + creatureStats.getAiSequence().stack(AiCombat(ptr)); + creatureStats.setHostile(true); + creatureStats.getAiSequence().execute(*it,0); + } + else if (type == OT_Assault) + { + creatureStats.getAiSequence().stack(AiCombat(ptr)); + creatureStats.setHostile(true); + creatureStats.getAiSequence().execute(*it,0); + } + + // Tell everyone else + for (std::vector::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1) + { + if (it == it1 || // Don't tell the witness or the player + ptr == *it1) + continue; + + // Is it a guard? Or will the witness fight? + CreatureStats& creatureStats1 = MWWorld::Class::get(*it1).getCreatureStats(*it1); + if (it1->getClass().isClass(*it1, "Guard")) + { + // TODO: Persue player, concider bounty? + creatureStats1.getAiSequence().stack(AiActivate(ptr.getClass().getId(ptr), 1)); + creatureStats1.getAiSequence().execute(*it1,0); + continue; + } + else if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) + { + creatureStats1.getAiSequence().stack(AiCombat(ptr)); + creatureStats1.setHostile(true); + creatureStats1.getAiSequence().execute(*it1,0); + } + else if (type == OT_Assault) + { + creatureStats.getAiSequence().stack(AiCombat(ptr)); + creatureStats.setHostile(true); + creatureStats.getAiSequence().execute(*it,0); + } + } + + break; // Someone saw the crime and everyone has been told } } } - if (reported) - reportCrime(ptr, victim, type, arg); return reported; } void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) { const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); + // Bounty for each type of crime if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) arg = store.find("iCrimeTresspass")->getInt(); @@ -849,32 +931,10 @@ namespace MWMechanics else if (type == OT_Theft) arg *= store.find("fCrimeStealing")->getFloat(); - // TODO: In some cases (type == Assault), if no NPCs are within earshot, the report will have no effect. - // however other crime types seem to be always produce a bounty. - MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}"); ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty() + arg); - if (!victim.isEmpty()) - { - int fight = 0; - // Increase in fight rating for each type of crime - if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) - fight = store.find("iFightTrespass")->getFloat(); - else if (type == OT_Pickpocket) - fight = store.find("iFightPickpocket")->getInt(); - else if (type == OT_Assault) - fight = store.find("iFightAttack")->getInt(); - else if (type == OT_Murder) - fight = store.find("iFightKilling")->getInt(); - else if (type == OT_Theft) - fight = store.find("fFightStealing")->getFloat(); - // Not sure if this should be permanent? - fight = victim.getClass().getCreatureStats(victim).getAiSetting(CreatureStats::AI_Fight).getBase() + fight; - victim.getClass().getCreatureStats(victim).setAiSetting(CreatureStats::AI_Fight, fight); - } - // If committing a crime against a faction member, expell from the faction if (!victim.isEmpty() && victim.getClass().isNpc()) { @@ -886,8 +946,6 @@ namespace MWMechanics ptr.getClass().getNpcStats(ptr).expell(factionID); } } - - // TODO: make any guards in the area try to arrest the player } bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 8314d011a..898788bf1 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -47,7 +47,7 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; i instance); virtual int getBaseGold(const MWWorld::Ptr& ptr) const; + + virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; }; }