mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-29 03:26:38 +00:00 
			
		
		
		
	Feature 1154 & 73: NPCs react to crime
This commit is contained in:
		
							parent
							
								
									8ce938c6f1
								
							
						
					
					
						commit
						4037f3705e
					
				
					 9 changed files with 140 additions and 62 deletions
				
			
		|  | @ -1297,6 +1297,11 @@ namespace MWClass | ||||||
|             return ref->mBase->mNpdt12.mGold; |             return ref->mBase->mNpdt12.mGold; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     bool Npc::isClass(const MWWorld::Ptr& ptr, const std::string &className) const | ||||||
|  |     { | ||||||
|  |         return ptr.get<ESM::NPC>()->mBase->mClass == className; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const ESM::GameSetting *Npc::fMinWalkSpeed; |     const ESM::GameSetting *Npc::fMinWalkSpeed; | ||||||
|     const ESM::GameSetting *Npc::fMaxWalkSpeed; |     const ESM::GameSetting *Npc::fMaxWalkSpeed; | ||||||
|     const ESM::GameSetting *Npc::fEncumberedMoveEffect; |     const ESM::GameSetting *Npc::fEncumberedMoveEffect; | ||||||
|  |  | ||||||
|  | @ -168,6 +168,8 @@ namespace MWClass | ||||||
|             ///< Write additional state from \a ptr into \a state.
 |             ///< Write additional state from \a ptr into \a state.
 | ||||||
| 
 | 
 | ||||||
|             virtual int getBaseGold(const MWWorld::Ptr& ptr) const; |             virtual int getBaseGold(const MWWorld::Ptr& ptr) const; | ||||||
|  | 
 | ||||||
|  |             virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -10,8 +10,9 @@ | ||||||
| #include "steering.hpp" | #include "steering.hpp" | ||||||
| #include "movement.hpp" | #include "movement.hpp" | ||||||
| 
 | 
 | ||||||
| MWMechanics::AiActivate::AiActivate(const std::string &objectId) | MWMechanics::AiActivate::AiActivate(const std::string &objectId, int arg) | ||||||
|     : mObjectId(objectId) |     : mObjectId(objectId), | ||||||
|  |       mArg(arg) | ||||||
| { | { | ||||||
| } | } | ||||||
| MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const | 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); |     Movement &movement = actor.getClass().getMovementSettings(actor); | ||||||
|     const ESM::Cell *cell = actor.getCell()->getCell(); |     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(); |     MWWorld::Ptr player = world->getPlayerPtr(); | ||||||
|     if(cell->mData.mX != player.getCell()->getCell()->mData.mX) |     if(cell->mData.mX != player.getCell()->getCell()->mData.mX) | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -1,25 +1,26 @@ | ||||||
| #ifndef GAME_MWMECHANICS_AIACTIVATE_H | #ifndef GAME_MWMECHANICS_AIACTIVATE_H | ||||||
| #define GAME_MWMECHANICS_AIACTIVATE_H | #define GAME_MWMECHANICS_AIACTIVATE_H | ||||||
| 
 | 
 | ||||||
| #include "aipackage.hpp" | #include "aipackage.hpp" | ||||||
| #include <string> | #include <string> | ||||||
| 
 | 
 | ||||||
| #include "pathfinding.hpp" | #include "pathfinding.hpp" | ||||||
| 
 | 
 | ||||||
| namespace MWMechanics | namespace MWMechanics | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
|     class AiActivate : public AiPackage |     class AiActivate : public AiPackage | ||||||
|     { |     { | ||||||
|         public: |         public: | ||||||
|             AiActivate(const std::string &objectId); |             AiActivate(const std::string &objectId, int arg); | ||||||
|             virtual AiActivate *clone() const; |             virtual AiActivate *clone() const; | ||||||
|             virtual bool execute (const MWWorld::Ptr& actor,float duration); |             virtual bool execute (const MWWorld::Ptr& actor,float duration); | ||||||
|                     ///< \return Package completed?
 |                     ///< \return Package completed?
 | ||||||
|             virtual int getTypeId() const; |             virtual int getTypeId() const; | ||||||
| 
 | 
 | ||||||
|         private: |         private: | ||||||
|             std::string mObjectId; |             std::string mObjectId; | ||||||
|  |             int mArg; | ||||||
| 
 | 
 | ||||||
|             PathFinder mPathFinder; |             PathFinder mPathFinder; | ||||||
|             int mCellX; |             int mCellX; | ||||||
|  |  | ||||||
|  | @ -161,7 +161,7 @@ void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list) | ||||||
|         else if (it->mType == ESM::AI_Activate) |         else if (it->mType == ESM::AI_Activate) | ||||||
|         { |         { | ||||||
|             ESM::AIActivate data = it->mActivate; |             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)
 |         else //if (it->mType == ESM::AI_Follow)
 | ||||||
|         { |         { | ||||||
|  |  | ||||||
|  | @ -12,6 +12,9 @@ | ||||||
| #include "../mwworld/class.hpp" | #include "../mwworld/class.hpp" | ||||||
| #include "../mwworld/player.hpp" | #include "../mwworld/player.hpp" | ||||||
| 
 | 
 | ||||||
|  | #include "aicombat.hpp" | ||||||
|  | #include "aiactivate.hpp" | ||||||
|  | 
 | ||||||
| #include <OgreSceneNode.h> | #include <OgreSceneNode.h> | ||||||
| 
 | 
 | ||||||
| #include "spellcasting.hpp" | #include "spellcasting.hpp" | ||||||
|  | @ -801,42 +804,121 @@ namespace MWMechanics | ||||||
| 
 | 
 | ||||||
|     bool MechanicsManager::commitCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) |     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; |             return false; | ||||||
| 
 | 
 | ||||||
|         bool reported=false; |         const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); | ||||||
|         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"); |  | ||||||
|                 } |  | ||||||
| 
 | 
 | ||||||
|                 // Actor has witnessed a crime. Will he report it?
 |         // What amount of alarm did this crime generate?
 | ||||||
|                 // (not sure, is > 0 correct?)
 |         int alarm; | ||||||
|                 if (it->first.getClass().getCreatureStats(it->first).getAiSetting(CreatureStats::AI_Alarm).getModified() > 0) |         if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) | ||||||
|  |             alarm = esmStore.get<ESM::GameSetting>().find("iAlarmTresspass")->getInt(); | ||||||
|  |         else if (type == OT_Pickpocket) | ||||||
|  |             alarm = esmStore.get<ESM::GameSetting>().find("iAlarmPickPocket")->getInt(); | ||||||
|  |         else if (type == OT_Assault) | ||||||
|  |             alarm = esmStore.get<ESM::GameSetting>().find("iAlarmAttack")->getInt(); | ||||||
|  |         else if (type == OT_Murder) | ||||||
|  |             alarm = esmStore.get<ESM::GameSetting>().find("iAlarmKilling")->getInt(); | ||||||
|  |         else if (type == OT_Theft) | ||||||
|  |             alarm = esmStore.get<ESM::GameSetting>().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<MWWorld::Ptr> neighbors; | ||||||
|  |         mActors.getObjectsInRange(Ogre::Vector3(ptr.getRefData().getPosition().pos),  | ||||||
|  |             esmStore.get<ESM::GameSetting>().find("fAlarmRadius")->getInt(), neighbors); | ||||||
|  |          | ||||||
|  |         // Did anyone see the crime?
 | ||||||
|  |         for (std::vector<MWWorld::Ptr>::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
 |                     creatureStats.setAlarmed(true);  | ||||||
|                     // fAlarmRadius ?
 |                     reported = true; | ||||||
|                     reported=true; |                     reportCrime(ptr, victim, type, arg); | ||||||
|                     break; | 
 | ||||||
|  |                     // 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<MWWorld::Ptr>::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; |         return reported; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) |     void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) | ||||||
|     { |     { | ||||||
|         const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); |         const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); | ||||||
|  |          | ||||||
|         // Bounty for each type of crime
 |         // Bounty for each type of crime
 | ||||||
|         if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) |         if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) | ||||||
|             arg = store.find("iCrimeTresspass")->getInt(); |             arg = store.find("iCrimeTresspass")->getInt(); | ||||||
|  | @ -849,32 +931,10 @@ namespace MWMechanics | ||||||
|         else if (type == OT_Theft) |         else if (type == OT_Theft) | ||||||
|             arg *= store.find("fCrimeStealing")->getFloat(); |             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}"); |         MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}"); | ||||||
|         ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty() |         ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty() | ||||||
|                                                   + arg); |                                                   + 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 committing a crime against a faction member, expell from the faction
 | ||||||
|         if (!victim.isEmpty() && victim.getClass().isNpc()) |         if (!victim.isEmpty() && victim.getClass().isNpc()) | ||||||
|         { |         { | ||||||
|  | @ -886,8 +946,6 @@ namespace MWMechanics | ||||||
|                 ptr.getClass().getNpcStats(ptr).expell(factionID); |                 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) |     bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) | ||||||
|  |  | ||||||
|  | @ -47,7 +47,7 @@ namespace MWScript | ||||||
|                     // discard additional arguments (reset), because we have no idea what they mean.
 |                     // discard additional arguments (reset), because we have no idea what they mean.
 | ||||||
|                     for (unsigned int i=0; i<arg0; ++i) runtime.pop(); |                     for (unsigned int i=0; i<arg0; ++i) runtime.pop(); | ||||||
| 
 | 
 | ||||||
|                     MWMechanics::AiActivate activatePackage(objectID); |                     MWMechanics::AiActivate activatePackage(objectID, 0); | ||||||
|                     MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(activatePackage); |                     MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(activatePackage); | ||||||
|                     std::cout << "AiActivate" << std::endl; |                     std::cout << "AiActivate" << std::endl; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -401,4 +401,9 @@ namespace MWWorld | ||||||
|     { |     { | ||||||
|         throw std::runtime_error("class does not support base gold"); |         throw std::runtime_error("class does not support base gold"); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     bool Class::isClass(const MWWorld::Ptr& ptr, const std::string &className) const | ||||||
|  |     { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -334,6 +334,8 @@ namespace MWWorld | ||||||
|             static void registerClass (const std::string& key,  boost::shared_ptr<Class> instance); |             static void registerClass (const std::string& key,  boost::shared_ptr<Class> instance); | ||||||
| 
 | 
 | ||||||
|             virtual int getBaseGold(const MWWorld::Ptr& ptr) const; |             virtual int getBaseGold(const MWWorld::Ptr& ptr) const; | ||||||
|  | 
 | ||||||
|  |             virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue