diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index ea4756ac4d..9fabb20806 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -67,7 +67,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects - drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow + drawstate spells activespells npcstats aipackage aisequence aipersue alchemy aiwander aitravel aifollow aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting disease pickpocket levelledlist combat steering ) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d41f7002af..5a3ff10de4 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -812,7 +812,13 @@ namespace MWClass return boost::shared_ptr(new MWWorld::FailedAction("#{sActorInCombat}")); if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)) return boost::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing + + // player got activated by another NPC + if(ptr.getRefData().getHandle() == "player") + return boost::shared_ptr(new MWWorld::ActionTalk(actor)); + return boost::shared_ptr(new MWWorld::ActionTalk(ptr)); + } MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr) @@ -1297,6 +1303,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 fb45a2f1f6..596bf0e56b 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/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d06da30c28..f7478e22c3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -29,6 +29,7 @@ #include "aicombat.hpp" #include "aifollow.hpp" +#include "aipersue.hpp" namespace { @@ -175,14 +176,16 @@ namespace MWMechanics adjustMagicEffects (ptr); if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) calculateDynamicStats (ptr); + calculateCreatureStatModifiers (ptr, duration); // AI if(MWBase::Environment::get().getMechanicsManager()->isAIActive()) { - CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - //engage combat or not? + CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + //engage combat or not? if(ptr != player && !creatureStats.isHostile()) { ESM::Position playerpos = player.getRefData().getPosition(); @@ -218,7 +221,7 @@ namespace MWMechanics } } } - + updateCrimePersuit(ptr, duration); creatureStats.getAiSequence().execute (ptr,duration); } @@ -717,6 +720,72 @@ namespace MWMechanics } } + void Actors::updateCrimePersuit(const MWWorld::Ptr& ptr, float duration) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (ptr != player && ptr.getClass().isNpc()) + { + // get stats of witness + CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); + NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats(ptr); + + // If I'm a guard and I'm not hostile + if (ptr.getClass().isClass(ptr, "Guard") && !creatureStats.isHostile()) + { + /// \todo Move me! I shouldn't be here... + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + float cutoff = float(esmStore.get().find("iCrimeThreshold")->getInt()) * + float(esmStore.get().find("iCrimeThresholdMultiplier")->getInt()) * + esmStore.get().find("fCrimeGoldDiscountMult")->getFloat(); + // Attack on sight if bounty is greater than the cutoff + if ( player.getClass().getNpcStats(player).getBounty() >= cutoff + && MWBase::Environment::get().getWorld()->getLOS(ptr, player) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) + { + creatureStats.getAiSequence().stack(AiCombat(player)); + creatureStats.setHostile(true); + npcStats.setCrimeId( MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() ); + } + } + + // if I was a witness to a crime + if (npcStats.getCrimeId() != -1) + { + // if you've payed for your crimes and I havent noticed + if( npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() ) + { + // Calm witness down + if (ptr.getClass().isClass(ptr, "Guard")) + creatureStats.getAiSequence().stopPersue(); + creatureStats.getAiSequence().stopCombat(); + + // Reset factors to attack + // TODO: Not a complete list, disposition changes? + creatureStats.setHostile(false); + creatureStats.setAttacked(false); + + // Update witness crime id + npcStats.setCrimeId(-1); + } + else if (!creatureStats.isHostile()) + { + if (ptr.getClass().isClass(ptr, "Guard")) + creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player))); + else + creatureStats.getAiSequence().stack(AiCombat(player)); + creatureStats.setHostile(true); + } + } + + // if I didn't report a crime was I attacked? + else if (creatureStats.getAttacked() && !creatureStats.isHostile()) + { + creatureStats.getAiSequence().stack(AiCombat(player)); + creatureStats.setHostile(true); + } + } + } + Actors::Actors() {} Actors::~Actors() diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 4b18ac862a..d61d742587 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -42,6 +42,8 @@ namespace MWMechanics void updateEquippedLight (const MWWorld::Ptr& ptr, float duration); + void updateCrimePersuit (const MWWorld::Ptr& ptr, float duration); + public: Actors(); diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 7c94c25896..fd54869f6c 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -1,24 +1,24 @@ #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); + 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; PathFinder mPathFinder; diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 74c77bf978..8e015da158 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -19,7 +19,8 @@ namespace MWMechanics TypeIdEscort = 2, TypeIdFollow = 3, TypeIdActivate = 4, - TypeIdCombat = 5 + TypeIdCombat = 5, + TypeIdPersue = 6 }; virtual ~AiPackage(); diff --git a/apps/openmw/mwmechanics/aipersue.cpp b/apps/openmw/mwmechanics/aipersue.cpp new file mode 100644 index 0000000000..36e18946c3 --- /dev/null +++ b/apps/openmw/mwmechanics/aipersue.cpp @@ -0,0 +1,106 @@ +#include "aipersue.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/action.hpp" +#include "../mwworld/cellstore.hpp" + +#include "steering.hpp" +#include "movement.hpp" +#include "creaturestats.hpp" + +MWMechanics::AiPersue::AiPersue(const std::string &objectId) + : mObjectId(objectId) +{ +} +MWMechanics::AiPersue *MWMechanics::AiPersue::clone() const +{ + return new AiPersue(*this); +} +bool MWMechanics::AiPersue::execute (const MWWorld::Ptr& actor, float duration) +{ + MWBase::World *world = MWBase::Environment::get().getWorld(); + ESM::Position pos = actor.getRefData().getPosition(); + Movement &movement = actor.getClass().getMovementSettings(actor); + const ESM::Cell *cell = actor.getCell()->getCell(); + + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + + MWWorld::Ptr player = world->getPlayerPtr(); + if(cell->mData.mX != player.getCell()->getCell()->mData.mX) + { + int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); + //check if actor is near the border of an inactive cell. If so, stop walking. + if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > + sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) + { + movement.mPosition[1] = 0; + return false; + } + } + if(cell->mData.mY != player.getCell()->getCell()->mData.mY) + { + int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); + //check if actor is near the border of an inactive cell. If so, stop walking. + if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > + sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) + { + movement.mPosition[1] = 0; + return false; + } + } + + MWWorld::Ptr target = world->getPtr(mObjectId,false); + ESM::Position targetPos = target.getRefData().getPosition(); + + bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; + if(!mPathFinder.isPathConstructed() || cellChange) + { + mCellX = cell->mData.mX; + mCellY = cell->mData.mY; + + ESM::Pathgrid::Point dest; + dest.mX = targetPos.pos[0]; + dest.mY = targetPos.pos[1]; + dest.mZ = targetPos.pos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + mPathFinder.buildPath(start, dest, actor.getCell(), true); + } + + if((pos.pos[0]-targetPos.pos[0])*(pos.pos[0]-targetPos.pos[0])+ + (pos.pos[1]-targetPos.pos[1])*(pos.pos[1]-targetPos.pos[1])+ + (pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 200*200) + { + movement.mPosition[1] = 0; + MWWorld::Ptr target = world->getPtr(mObjectId,false); + MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); + return true; + } + + if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) + { + movement.mPosition[1] = 0; + MWWorld::Ptr target = world->getPtr(mObjectId,false); + MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); + return true; + } + + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + zTurn(actor, Ogre::Degree(zAngle)); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + movement.mPosition[1] = 1; + + return false; +} + +int MWMechanics::AiPersue::getTypeId() const +{ + return TypeIdPersue; +} diff --git a/apps/openmw/mwmechanics/aipersue.hpp b/apps/openmw/mwmechanics/aipersue.hpp new file mode 100644 index 0000000000..3fd708ab32 --- /dev/null +++ b/apps/openmw/mwmechanics/aipersue.hpp @@ -0,0 +1,29 @@ +#ifndef GAME_MWMECHANICS_AIPERSUE_H +#define GAME_MWMECHANICS_AIPERSUE_H + +#include "aipackage.hpp" +#include + +#include "pathfinding.hpp" + +namespace MWMechanics +{ + + class AiPersue : public AiPackage + { + public: + AiPersue(const std::string &objectId); + virtual AiPersue *clone() const; + virtual bool execute (const MWWorld::Ptr& actor,float duration); + ///< \return Package completed? + virtual int getTypeId() const; + + private: + std::string mObjectId; + + PathFinder mPathFinder; + int mCellX; + int mCellY; + }; +} +#endif diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 2110393fdc..c67367a6c7 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -73,6 +73,15 @@ void MWMechanics::AiSequence::stopCombat() } } +void MWMechanics::AiSequence::stopPersue() +{ + while (getTypeId() == AiPackage::TypeIdPersue) + { + delete *mPackages.begin(); + mPackages.erase (mPackages.begin()); + } +} + bool MWMechanics::AiSequence::isPackageDone() const { return mDone; diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 62f48f981e..07b7c898ce 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -49,6 +49,9 @@ namespace MWMechanics void stopCombat(); ///< Removes all combat packages until first non-combat or stack empty. + + void stopPersue(); + ///< Removes all persue packages until first non-persue or stack empty. bool isPackageDone() const; ///< Has a package been completed during the last update? diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index feed8d182b..f81613ed1f 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -435,8 +435,9 @@ namespace MWMechanics return getMovementFlag (Flag_Run) || getMovementFlag (Flag_ForceRun); case Stance_Sneak: return getMovementFlag (Flag_Sneak) || getMovementFlag (Flag_ForceSneak); + default: + return false; } - return false; // shut up, compiler } DrawState_ CreatureStats::getDrawState() const diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 20a9a5799e..5dc59e5abb 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -175,15 +175,12 @@ namespace MWMechanics void talkedToPlayer(); bool isAlarmed() const; - void setAlarmed (bool alarmed); bool getAttacked() const; - void setAttacked (bool attacked); bool isHostile() const; - void setHostile (bool hostile); bool getCreatureTargetted() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 3164ca1550..3a26ea7dc5 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1,5 +1,6 @@ #include "mechanicsmanagerimp.hpp" +#include "npcstats.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" @@ -802,34 +803,74 @@ 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 NPCs within the alarm radius + std::vector neighbors; + mActors.getObjectsInRange(Ogre::Vector3(ptr.getRefData().getPosition().pos), + esmStore.get().find("fAlarmRadius")->getInt(), neighbors); + + // Find an actor who witnessed the crime + for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) + { + if (*it == ptr) continue; // not the player + + // Was the crime seen? + if ( ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) || + type == OT_Assault ) + { + + // Will the witness report the crime? + if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { - // TODO: stats.setAlarmed(true) on NPCs within earshot - // fAlarmRadius ? - reported=true; - break; + reported = true; + int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); + + // Tell everyone, including yourself + for (std::vector::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1) + { + if (*it1 == ptr) continue; // not the player + + // TODO: Add more messages + if (type == OT_Theft) + MWBase::Environment::get().getDialogueManager()->say(*it1, "thief"); + else if (type == OT_Assault) + MWBase::Environment::get().getDialogueManager()->say(*it1, "attack"); + + // Will other witnesses paticipate in crime + if ( it1->getClass().getCreatureStats(*it1).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm + || type == OT_Assault ) + { + it1->getClass().getNpcStats(*it1).setCrimeId(id); + } + } + break; // Someone saw the crime and everyone has been told } } } - if (reported) reportCrime(ptr, victim, type, arg); return reported; @@ -838,6 +879,7 @@ namespace MWMechanics 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(); @@ -850,32 +892,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()) { @@ -887,8 +907,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/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 74587a626f..e11e3b0c48 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -29,6 +29,7 @@ MWMechanics::NpcStats::NpcStats() , mLevelProgress(0) , mDisposition(0) , mReputation(0) +, mCrimeId(-1) , mWerewolfKills (0) , mProfit(0) , mTimeToStartDrowning(20.0) @@ -340,6 +341,16 @@ void MWMechanics::NpcStats::setReputation(int reputation) mReputation = reputation; } +int MWMechanics::NpcStats::getCrimeId() const +{ + return mCrimeId; +} + +void MWMechanics::NpcStats::setCrimeId(int id) +{ + mCrimeId = id; +} + bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int rank) const { if (rank<0 || rank>=10) @@ -441,6 +452,8 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const mWerewolfSkill[i].writeState (state.mSkills[i].mWerewolf); } + state.mCrimeId = mCrimeId; + state.mBounty = mBounty; for (std::set::const_iterator iter (mExpelled.begin()); @@ -493,6 +506,7 @@ void MWMechanics::NpcStats::readState (const ESM::NpcStats& state) mWerewolfSkill[i].readState (state.mSkills[i].mWerewolf); } + mCrimeId = state.mCrimeId; mBounty = state.mBounty; mReputation = state.mReputation; mWerewolfKills = state.mWerewolfKills; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index ad493be3c1..0ae596a54d 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -37,6 +37,7 @@ namespace MWMechanics std::set mExpelled; std::map mFactionReputation; int mReputation; + int mCrimeId; int mWerewolfKills; int mProfit; float mAttackStrength; @@ -63,13 +64,14 @@ namespace MWMechanics void modifyProfit(int diff); int getBaseDisposition() const; - void setBaseDisposition(int disposition); int getReputation() const; - void setReputation(int reputation); + int getCrimeId() const; + void setCrimeId(int id); + const SkillValue& getSkill (int index) const; SkillValue& getSkill (int index); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 89a5ceec1e..831ec77369 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -21,6 +21,7 @@ #include "../mwbase/scriptmanager.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" @@ -802,6 +803,7 @@ namespace MWScript { MWBase::World* world = MWBase::Environment::get().getWorld(); world->goToJail(); + MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; @@ -812,7 +814,7 @@ namespace MWScript { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty(0); - MWBase::Environment::get().getWorld()->confiscateStolenItems(player); + MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; @@ -823,6 +825,8 @@ namespace MWScript { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty(0); + MWBase::Environment::get().getWorld()->confiscateStolenItems(player); + MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 8c61e32f44..99dbcc66c5 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -401,4 +401,9 @@ namespace MWWorld { throw std::runtime_error("class does not support base gold"); } + + bool Class::isClass(const MWWorld::Ptr& ptr, const std::string &className) const + { + return false; + } } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index cee1b171de..27b8423484 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -334,6 +334,8 @@ namespace MWWorld static void registerClass (const std::string& key, boost::shared_ptr instance); 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/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 6d551ecf1e..a4a4e95684 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -28,9 +28,11 @@ namespace MWWorld : mCellStore(0), mLastKnownExteriorPosition(0,0,0), mAutoMove(false), - mForwardBackward (0), + mForwardBackward(0), mTeleported(false), - mMarkedCell(NULL) + mMarkedCell(NULL), + mCurrentCrimeId(-1), + mPayedCrimeId(-1) { mPlayer.mBase = player; mPlayer.mRef.mRefID = "player"; @@ -194,6 +196,9 @@ namespace MWWorld mPlayer.save (player.mObject); player.mCellId = mCellStore->getCell()->getCellId(); + player.mCurrentCrimeId = mCurrentCrimeId; + player.mPayedCrimeId = mPayedCrimeId; + player.mBirthsign = mSign; player.mLastKnownExteriorPosition[0] = mLastKnownExteriorPosition.x; @@ -239,6 +244,9 @@ namespace MWWorld !world.getStore().get().search (player.mBirthsign)) throw std::runtime_error ("invalid player state record (birthsign)"); + mCurrentCrimeId = player.mCurrentCrimeId; + mPayedCrimeId = player.mPayedCrimeId; + mSign = player.mBirthsign; mLastKnownExteriorPosition.x = player.mLastKnownExteriorPosition[0]; @@ -274,4 +282,19 @@ namespace MWWorld return false; } + + int Player::getNewCrimeId() + { + return ++mCurrentCrimeId; + } + + void Player::recordCrimeId() + { + mPayedCrimeId = mCurrentCrimeId; + } + + int Player::getCrimeId() const + { + return mPayedCrimeId; + } } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 7eb023a2b1..7dbaaddb43 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -41,6 +41,10 @@ namespace MWWorld bool mAutoMove; int mForwardBackward; bool mTeleported; + + int mCurrentCrimeId; // the id assigned witnesses + int mPayedCrimeId; // the last id payed off (0 bounty) + public: Player(const ESM::NPC *player, const MWBase::World& world); @@ -63,15 +67,12 @@ namespace MWWorld MWWorld::Ptr getPlayer(); void setBirthSign(const std::string &sign); - const std::string &getBirthSign() const; void setDrawState (MWMechanics::DrawState_ state); - - bool getAutoMove() const; - MWMechanics::DrawState_ getDrawState(); /// \todo constness + bool getAutoMove() const; void setAutoMove (bool enable); void setLeftRight (int value); @@ -94,6 +95,10 @@ namespace MWWorld void write (ESM::ESMWriter& writer) const; bool readRecord (ESM::ESMReader& reader, int32_t type); + + int getNewCrimeId(); // get new id for witnesses + void recordCrimeId(); // record the payed crime id when bounty is 0 + int getCrimeId() const; // get the last payed crime id }; } #endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d56ca1f160..e3c6325126 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2791,6 +2791,8 @@ namespace MWWorld message += "\n" + skillMsg; } + // TODO: Sleep the player + std::vector buttons; buttons.push_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index 531424ab23..80238ad684 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -67,6 +67,9 @@ void ESM::NpcStats::load (ESMReader &esm) mLevelHealthBonus = 0; esm.getHNOT (mLevelHealthBonus, "LVLH"); + + mCrimeId = -1; + esm.getHNOT (mCrimeId, "CRID"); } void ESM::NpcStats::save (ESMWriter &esm) const @@ -130,4 +133,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const if (mLevelHealthBonus) esm.writeHNT ("LVLH", mLevelHealthBonus); + + if (mCrimeId != -1) + esm.writeHNT ("CRID", mCrimeId); } \ No newline at end of file diff --git a/components/esm/npcstats.hpp b/components/esm/npcstats.hpp index b3f70db252..504cd0163c 100644 --- a/components/esm/npcstats.hpp +++ b/components/esm/npcstats.hpp @@ -45,6 +45,7 @@ namespace ESM float mTimeToStartDrowning; float mLastDrowningHit; float mLevelHealthBonus; + int mCrimeId; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/player.cpp b/components/esm/player.cpp index d5ddc74d0a..e41cc535e5 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -25,6 +25,9 @@ void ESM::Player::load (ESMReader &esm) esm.getHNOT (mAutoMove, "AMOV"); mBirthsign = esm.getHNString ("SIGN"); + + esm.getHNT (mCurrentCrimeId, "CURD"); + esm.getHNT (mPayedCrimeId, "PAYD"); } void ESM::Player::save (ESMWriter &esm) const @@ -45,4 +48,7 @@ void ESM::Player::save (ESMWriter &esm) const esm.writeHNT ("AMOV", mAutoMove); esm.writeHNString ("SIGN", mBirthsign); + + esm.writeHNT ("CURD", mCurrentCrimeId); + esm.writeHNT ("PAYD", mPayedCrimeId); } \ No newline at end of file diff --git a/components/esm/player.hpp b/components/esm/player.hpp index 0d70ee0904..377c8547ad 100644 --- a/components/esm/player.hpp +++ b/components/esm/player.hpp @@ -24,6 +24,9 @@ namespace ESM CellId mMarkedCell; unsigned char mAutoMove; std::string mBirthsign; + + int mCurrentCrimeId; + int mPayedCrimeId; void load (ESMReader &esm); void save (ESMWriter &esm) const;