From d8f24ac4998b0fb297a167e7b756089eb07400da Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 17 Feb 2014 15:49:49 +0100 Subject: [PATCH 01/85] bug fix --- apps/openmw/mwmechanics/aiactivate.cpp | 4 +++- apps/openmw/mwmechanics/aifollow.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 1f3c58521..2d5d11134 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -59,7 +59,9 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) } } - MWWorld::Ptr target = world->getPtr(mObjectId,false); + MWWorld::Ptr target = world->searchPtr(mObjectId,false); + if(target == MWWorld::Ptr()) return true; + ESM::Position targetPos = target.getRefData().getPosition(); bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index cf5291fd3..d3ee21abf 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -20,7 +20,9 @@ MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &ce bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); + + if(target == MWWorld::Ptr()) return false; mTimer = mTimer + duration; mStuckTimer = mStuckTimer + duration; From f4879dacd54bc7599e676a297297a40c58e4376e Mon Sep 17 00:00:00 2001 From: gus Date: Wed, 5 Mar 2014 11:24:39 +0100 Subject: [PATCH 02/85] add AIfollow to summoned creatures --- apps/openmw/mwmechanics/actors.cpp | 2 ++ apps/openmw/mwmechanics/aifollow.cpp | 40 +++++++++++++++++----------- apps/openmw/mwmechanics/aifollow.hpp | 2 ++ 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 1fb22ce63..e28ce4b27 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -539,6 +539,8 @@ namespace MWMechanics ref.getPtr().getCellRef().mPos = ipos; // TODO: Add AI to follow player and fight for him + AiFollow package(ptr.getRefData().getHandle()); + MWWorld::Class::get (ref.getPtr()).getCreatureStats (ref.getPtr()).getAiSequence().stack(package); // TODO: VFX_SummonStart, VFX_SummonEnd creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle())); diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index d3ee21abf..5e4559c0e 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -10,11 +10,16 @@ #include "steering.hpp" MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) +: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) { } MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0) +: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0) +{ +} + +MWMechanics::AiFollow::AiFollow(const std::string &actorId) +: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) { } @@ -30,22 +35,25 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) ESM::Position pos = actor.getRefData().getPosition(); - if(mTotalTime > mDuration && mDuration != 0) - return true; - - if((pos.pos[0]-mX)*(pos.pos[0]-mX) + - (pos.pos[1]-mY)*(pos.pos[1]-mY) + - (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) + if(!mAlwaysFollow) { - if(actor.getCell()->isExterior()) - { - if(mCellId == "") - return true; - } - else + if(mTotalTime > mDuration && mDuration != 0) + return true; + + if((pos.pos[0]-mX)*(pos.pos[0]-mX) + + (pos.pos[1]-mY)*(pos.pos[1]-mY) + + (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) { - if(mCellId == actor.getCell()->mCell->mName) - return true; + if(actor.getCell()->isExterior()) + { + if(mCellId == "") + return true; + } + else + { + if(mCellId == actor.getCell()->mCell->mName) + return true; + } } } diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 9d77b903d..48a8eb4c2 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -14,6 +14,7 @@ namespace MWMechanics public: AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); + AiFollow(const std::string &ActorId); virtual AiFollow *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? @@ -22,6 +23,7 @@ namespace MWMechanics std::string getFollowedActor(); private: + bool mAlwaysFollow; //this will make the actor always follow, thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). float mDuration; float mX; float mY; From d84319300a45caf5bd7c03e468ec557c6f3681c4 Mon Sep 17 00:00:00 2001 From: gus Date: Wed, 5 Mar 2014 11:27:16 +0100 Subject: [PATCH 03/85] fix --- apps/openmw/mwmechanics/aifollow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 5e4559c0e..e2a96fc87 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -27,7 +27,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); - if(target == MWWorld::Ptr()) return false; + if(target == MWWorld::Ptr()) return true; mTimer = mTimer + duration; mStuckTimer = mStuckTimer + duration; From 895748f18d2f5d66e0f0033f4a49ad7fd44857bb Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Fri, 28 Mar 2014 12:01:56 -0400 Subject: [PATCH 04/85] Gold Pool implemented for Vendors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It appears that my solution breaks persuasion gold for some reason. I may be wrong. I can’t see where this could be happening as the files I’ve changes should not affect persuasion at all. --- apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwgui/tradewindow.cpp | 21 +++++++++------------ 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 72b315484..2af134a38 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -136,7 +136,7 @@ namespace MWClass // TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory. // (except for gold you gave him) - getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, ref->mBase->mData.mGold, ptr); + //getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, ref->mBase->mData.mGold, ptr); if (ref->mBase->mFlags & ESM::Creature::Weapon) getInventoryStore(ptr).autoEquip(ptr); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 020f3b3af..f7b280a79 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -367,7 +367,7 @@ namespace MWClass // TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory. // (except for gold you gave him) - getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, gold, ptr); + //getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, gold, ptr); getInventoryStore(ptr).autoEquip(ptr); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 0525a97ae..93f53a11f 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -360,7 +360,8 @@ namespace MWGui if (mCurrentBalance != 0) { addOrRemoveGold(mCurrentBalance, player); - addOrRemoveGold(-mCurrentBalance, mPtr); + mPtr.getClass().getCreatureStats(mPtr).setGoldPool( + mPtr.getClass().getCreatureStats(mPtr).getGoldPool() - mCurrentBalance ); } updateTradeTime(); @@ -470,28 +471,24 @@ namespace MWGui int TradeWindow::getMerchantGold() { - int merchantGold = 0; - MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr); - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, MWWorld::ContainerStore::sGoldId)) - merchantGold += it->getRefData().getCount(); - } + int merchantGold = mPtr.getClass().getCreatureStats(mPtr).getGoldPool(); return merchantGold; } // Relates to NPC gold reset delay void TradeWindow::checkTradeTime() { - MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr); - const MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); + MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); double delay = boost::lexical_cast(MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getInt()); // if time stamp longer than gold reset delay, reset gold. if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getTradeTime() + delay) { - addOrRemoveGold(-store.count(MWWorld::ContainerStore::sGoldId), mPtr); - addOrRemoveGold(+sellerStats.getGoldPool(), mPtr); + // reset gold to the base gold + if ( mPtr.getClass().isNpc() ) + sellerStats.setGoldPool(mPtr.get()->mBase->mNpdt52.mGold); + else + sellerStats.setGoldPool(mPtr.get()->mBase->mData.mGold); } } From 401d21b4ee1693b13a4770961899dcbb6ab30cfd Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Fri, 28 Mar 2014 14:21:38 -0400 Subject: [PATCH 05/85] getBaseGold implemented in MWWorld::Class for NPC and Creature Implemented a getBaseGold() to get the vendor gold base NPC gold base now can come from mNpdt12 and mNpdt52 --- apps/openmw/mwclass/creature.cpp | 5 +++++ apps/openmw/mwclass/creature.hpp | 2 ++ apps/openmw/mwclass/npc.cpp | 9 +++++++++ apps/openmw/mwclass/npc.hpp | 2 ++ apps/openmw/mwgui/tradewindow.cpp | 6 +----- apps/openmw/mwworld/class.cpp | 2 ++ apps/openmw/mwworld/class.hpp | 2 ++ 7 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2af134a38..d347b2c04 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -811,6 +811,11 @@ namespace MWClass customData.mCreatureStats.writeState (state2.mCreatureStats); } + int Creature::getBaseGold(const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mData.mGold; + } + const ESM::GameSetting* Creature::fMinWalkSpeedCreature; const ESM::GameSetting* Creature::fMaxWalkSpeedCreature; const ESM::GameSetting *Creature::fEncumberedMoveEffect; diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index c1bcb8739..04c010c83 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -141,6 +141,8 @@ namespace MWClass virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. + + virtual int getBaseGold(const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index f7b280a79..c06e31556 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1294,6 +1294,15 @@ namespace MWClass static_cast (customData.mNpcStats).writeState (state2.mCreatureStats); } + int Npc::getBaseGold(const MWWorld::Ptr& ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return ref->mBase->mNpdt52.mGold; + else + return ref->mBase->mNpdt12.mGold; + } + 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 c54dd339a..fb45a2f1f 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -166,6 +166,8 @@ namespace MWClass virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. + + virtual int getBaseGold(const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 93f53a11f..3e15bcd78 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -484,11 +484,7 @@ namespace MWGui // if time stamp longer than gold reset delay, reset gold. if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getTradeTime() + delay) { - // reset gold to the base gold - if ( mPtr.getClass().isNpc() ) - sellerStats.setGoldPool(mPtr.get()->mBase->mNpdt52.mGold); - else - sellerStats.setGoldPool(mPtr.get()->mBase->mData.mGold); + sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr)); } } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 39d48f95b..af8de1326 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -396,4 +396,6 @@ namespace MWWorld void Class::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {} void Class::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const {} + + int Class::getBaseGold(const MWWorld::Ptr& ptr) const {} } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 739fd5942..cee1b171d 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -332,6 +332,8 @@ namespace MWWorld ///< If there is no class for this pointer, an exception is thrown. static void registerClass (const std::string& key, boost::shared_ptr instance); + + virtual int getBaseGold(const MWWorld::Ptr& ptr) const; }; } From 9efef31bb81ec944401cfa5e025207c69ef384f3 Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Fri, 28 Mar 2014 17:27:23 -0400 Subject: [PATCH 06/85] Feature #953 Trader Gold - Unused code/warning resolved Removed unused code getBaseGold throws proper error --- apps/openmw/mwclass/creature.cpp | 4 ---- apps/openmw/mwclass/npc.cpp | 8 +------- apps/openmw/mwworld/class.cpp | 5 ++++- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index d347b2c04..94238e6d5 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -134,10 +134,6 @@ namespace MWClass getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr), "", MWBase::Environment::get().getWorld()->getStore()); - // TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory. - // (except for gold you gave him) - //getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, ref->mBase->mData.mGold, ptr); - if (ref->mBase->mFlags & ESM::Creature::Weapon) getInventoryStore(ptr).autoEquip(ptr); } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c06e31556..d41f7002a 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -365,13 +365,7 @@ namespace MWClass // store ptr.getRefData().setCustomData (data.release()); - // TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory. - // (except for gold you gave him) - //getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, gold, ptr); - - getInventoryStore(ptr).autoEquip(ptr); - - + getInventoryStore(ptr).autoEquip(ptr); } } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index af8de1326..8c61e32f4 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -397,5 +397,8 @@ namespace MWWorld void Class::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const {} - int Class::getBaseGold(const MWWorld::Ptr& ptr) const {} + int Class::getBaseGold(const MWWorld::Ptr& ptr) const + { + throw std::runtime_error("class does not support base gold"); + } } From 13d330e42744e6200a91d922c12d1d35d6dcccd8 Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Fri, 28 Mar 2014 21:16:42 -0400 Subject: [PATCH 07/85] Feature #1233 Bribe gold not in inventory Gold is now put into inventory receivers when bribing. --- apps/openmw/mwgui/dialogue.cpp | 10 ++++++++++ apps/openmw/mwgui/dialogue.hpp | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 6b913f24a..6cc580fb6 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -64,16 +64,19 @@ namespace MWGui else if (sender == mBribe10Button) { player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 10, player); + mReceiver.getClass().getContainerStore(mReceiver).add(MWWorld::ContainerStore::sGoldId, 10, mReceiver); type = MWBase::MechanicsManager::PT_Bribe10; } else if (sender == mBribe100Button) { player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 100, player); + mReceiver.getClass().getContainerStore(mReceiver).add(MWWorld::ContainerStore::sGoldId, 100, mReceiver); type = MWBase::MechanicsManager::PT_Bribe100; } else /*if (sender == mBribe1000Button)*/ { player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 1000, player); + mReceiver.getClass().getContainerStore(mReceiver).add(MWWorld::ContainerStore::sGoldId, 10000, mReceiver); type = MWBase::MechanicsManager::PT_Bribe1000; } @@ -97,6 +100,12 @@ namespace MWGui mGoldLabel->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); } + // The receiver of the bribe + void PersuasionDialog::setReceiver(MWWorld::Ptr receiver) + { + mReceiver = receiver; + } + // -------------------------------------------------------------------------------------------------- Response::Response(const std::string &text, const std::string &title) @@ -371,6 +380,7 @@ namespace MWGui mPtr = actor; mTopicsList->setEnabled(true); setTitle(npcName); + mPersuasionDialog.setReceiver(mPtr); mTopicsList->clear(); diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index befbd6eee..242dae299 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -35,6 +35,10 @@ namespace MWGui virtual void open(); + // The receiver of the bribe + MWWorld::Ptr mReceiver; + void setReceiver(MWWorld::Ptr receiver); + private: MyGUI::Button* mCancelButton; MyGUI::Button* mAdmireButton; From 3e8f7b3bae9a3e23ad6953fbe79c199da79213aa Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Sat, 29 Mar 2014 02:59:47 -0400 Subject: [PATCH 08/85] made mReceiver private --- apps/openmw/mwgui/dialogue.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 242dae299..368140520 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -36,7 +36,6 @@ namespace MWGui virtual void open(); // The receiver of the bribe - MWWorld::Ptr mReceiver; void setReceiver(MWWorld::Ptr receiver); private: @@ -51,6 +50,9 @@ namespace MWGui void onCancel (MyGUI::Widget* sender); void onPersuade (MyGUI::Widget* sender); + + // The receiver of the bribe + MWWorld::Ptr mReceiver; }; From 90a813ad2cf1f55a54b018ebe1ad80d6007c49a5 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 24 Mar 2014 23:20:25 +1100 Subject: [PATCH 09/85] Allow interrupting a walking NPC to trigger a greeting. --- apps/openmw/mwmechanics/aiwander.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 2db875a01..26975ff1e 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -69,6 +69,7 @@ namespace MWMechanics return new AiWander(*this); } + // TODO: duration is passed in but never used, check if it is needed bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); @@ -102,20 +103,21 @@ namespace MWMechanics ESM::Position pos = actor.getRefData().getPosition(); + // Once off initialization to discover & store allowed node points for this actor. if(!mStoredAvailableNodes) { - mStoredAvailableNodes = true; mPathgrid = world->getStore().get().search(*actor.getCell()->getCell()); mCellX = actor.getCell()->getCell()->mData.mX; mCellY = actor.getCell()->getCell()->mData.mY; + // TODO: If there is no path does this actor get stuck forever? if(!mPathgrid) mDistance = 0; else if(mPathgrid->mPoints.empty()) mDistance = 0; - if(mDistance) + if(mDistance) // A distance value is initially passed into the constructor. { mXCell = 0; mYCell = 0; @@ -151,10 +153,13 @@ namespace MWMechanics } mCurrentNode = mAllowedNodes[index]; mAllowedNodes.erase(mAllowedNodes.begin() + index); + + mStoredAvailableNodes = true; // set only if successful in finding allowed nodes } } } + // TODO: Does this actor stay in one spot forever while in AiWander? if(mAllowedNodes.empty()) mDistance = 0; @@ -162,7 +167,7 @@ namespace MWMechanics if(mDistance && (mCellX != actor.getCell()->getCell()->mData.mX || mCellY != actor.getCell()->getCell()->mData.mY)) mDistance = 0; - if(mChooseAction) + if(mChooseAction) // Initially set true by the constructor. { mPlayedIdle = 0; unsigned short idleRoll = 0; @@ -208,7 +213,8 @@ namespace MWMechanics } } - if(mIdleNow) + // Allow interrupting a walking actor to trigger a greeting + if(mIdleNow || (mWalking && (mWalkState != State_Norm))) { // Play a random voice greeting if the player gets too close const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -222,6 +228,14 @@ namespace MWMechanics float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance( Ogre::Vector3(actor.getRefData().getPosition().pos)); + if(mWalking && playerDist <= helloDistance) + { + stopWalking(actor); + mMoveNow = false; + mWalking = false; + mWalkState = State_Norm; + } + if (!mSaidGreeting) { // TODO: check if actor is aware / has line of sight @@ -353,7 +367,7 @@ namespace MWMechanics if(mWalkState == State_Evade) { - //std::cout << "Stuck \""<= COUNT_BEFORE_RESET) // something has gone wrong, reset { - //std::cout << "Reset \""< Date: Thu, 27 Mar 2014 08:37:05 +1100 Subject: [PATCH 10/85] Prevent NPC suicides off silt the strider platform in Seyda Neen. Added some comments as well. There may be opportunities for some optimization but left that out for now. --- apps/openmw/mwmechanics/pathfinding.cpp | 208 +++++++++++------------- 1 file changed, 98 insertions(+), 110 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 3ecd40743..c0b78f3c7 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -57,97 +57,6 @@ namespace return closestIndex; } - /*std::list reconstructPath(const std::vector& graph,const ESM::Pathgrid* pathgrid, int lastNode,float xCell, float yCell) - { - std::list path; - while(graph[lastNode].parent != -1) - { - //std::cout << "not empty" << xCell; - ESM::Pathgrid::Point pt = pathgrid->mPoints[lastNode]; - pt.mX += xCell; - pt.mY += yCell; - path.push_front(pt); - lastNode = graph[lastNode].parent; - } - return path; - }*/ - - - - /*std::list buildPath2(const ESM::Pathgrid* pathgrid,int start,int goal,float xCell = 0, float yCell = 0) - { - std::vector graph; - for(unsigned int i = 0; i < pathgrid->mPoints.size(); i++) - { - Node node; - node.label = i; - node.parent = -1; - graph.push_back(node); - } - for(unsigned int i = 0; i < pathgrid->mEdges.size(); i++) - { - Edge edge; - edge.destination = pathgrid->mEdges[i].mV1; - edge.cost = distance(pathgrid->mPoints[pathgrid->mEdges[i].mV0],pathgrid->mPoints[pathgrid->mEdges[i].mV1]); - graph[pathgrid->mEdges[i].mV0].edges.push_back(edge); - edge.destination = pathgrid->mEdges[i].mV0; - graph[pathgrid->mEdges[i].mV1].edges.push_back(edge); - } - - std::vector g_score(pathgrid->mPoints.size(),-1.); - std::vector f_score(pathgrid->mPoints.size(),-1.); - - g_score[start] = 0; - f_score[start] = distance(pathgrid->mPoints[start],pathgrid->mPoints[goal]); - - std::list openset; - std::list closedset; - openset.push_back(start); - - int current = -1; - - while(!openset.empty()) - { - current = openset.front(); - openset.pop_front(); - - if(current == goal) break; - - closedset.push_back(current); - - for(int j = 0;jmPoints[dest],pathgrid->mPoints[goal]); - if(!isInOpenSet) - { - std::list::iterator it = openset.begin(); - for(it = openset.begin();it!= openset.end();it++) - { - if(g_score[*it]>g_score[dest]) - break; - } - openset.insert(it,dest); - } - } - } - } - - } - return reconstructPath(graph,pathgrid,current,xCell,yCell); - - }*/ - } namespace MWMechanics @@ -166,37 +75,83 @@ namespace MWMechanics mIsPathConstructed = false; } + /* + * NOTE: based on buildPath2(), please check git history if interested + * + * Populate mGraph with the cost of each allowed edge (measured in distance ^2) + * Any existing data in mGraph is wiped clean first. The node's parent is + * set with initial value of -1. The parent values are populated by aStarSearch(). + * mGSore and mFScore are also resized. + * + * + * mGraph[f].edges[n].destination = t + * + * f = point index of location "from" + * t = point index of location "to" + * n = index of edges from point f + * + * + * Example: (note from p(0) to p(2) not allowed) + * + * mGraph[0].edges[0].destination = 1 + * .edges[1].destination = 3 + * + * mGraph[1].edges[0].destination = 0 + * .edges[1].destination = 2 + * .edges[2].destination = 3 + * + * mGraph[2].edges[0].destination = 1 + * + * (etc, etc) + * + * + * low + * cost + * p(0) <---> p(1) <------------> p(2) + * ^ ^ + * | | + * | +-----> p(3) + * +----------------> + * high cost + */ void PathFinder::buildPathgridGraph(const ESM::Pathgrid* pathGrid) { mGraph.clear(); - mGScore.resize(pathGrid->mPoints.size(),-1); - mFScore.resize(pathGrid->mPoints.size(),-1); + // resize lists + mGScore.resize(pathGrid->mPoints.size(), -1); + mFScore.resize(pathGrid->mPoints.size(), -1); Node defaultNode; defaultNode.label = -1; defaultNode.parent = -1; mGraph.resize(pathGrid->mPoints.size(),defaultNode); + // initialise mGraph for(unsigned int i = 0; i < pathGrid->mPoints.size(); i++) { Node node; node.label = i; node.parent = -1; - mGraph[i] = node; + mGraph[i] = node; // TODO: old code used push_back(node), check if any difference } + // store the costs (measured in distance ^2) of each edge, in both directions for(unsigned int i = 0; i < pathGrid->mEdges.size(); i++) { Edge edge; + edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0], + pathGrid->mPoints[pathGrid->mEdges[i].mV1]); + // forward path of the edge edge.destination = pathGrid->mEdges[i].mV1; - edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0],pathGrid->mPoints[pathGrid->mEdges[i].mV1]); mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge); - edge.destination = pathGrid->mEdges[i].mV0; - mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); + // reverse path of the edge + // NOTE: These are redundant, ESM already contains the required reverse paths + //edge.destination = pathGrid->mEdges[i].mV0; + //mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); } mIsGraphConstructed = true; } void PathFinder::cleanUpAStar() { - for(int i=0;i (mGraph.size());i++) + for(int i = 0; i < static_cast (mGraph.size()); i++) { mGraph[i].parent = -1; mGScore[i] = -1; @@ -204,6 +159,25 @@ namespace MWMechanics } } + /* + * NOTE: based on buildPath2(), please check git history if interested + * + * Find the shortest path to the target goal using a well known algorithm. + * Uses mGraph which has pre-computed costs for allowed edges. It is assumed + * that mGraph is already constructed. The caller, i.e. buildPath(), needs + * to ensure this. + * + * Returns path (a list of pathgrid point indexes) which may be empty. + * + * openset - point indexes to be traversed, lowest cost at the front + * closedset - point indexes already traversed + * + * mGScore - past accumulated costs vector indexed by point index + * mFScore - future estimated costs vector indexed by point index + * these are resized by buildPathgridGraph() + * + * The heuristics used is distance^2 from current position to the final goal. + */ std::list PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell, float yCell) { cleanUpAStar(); @@ -218,18 +192,19 @@ namespace MWMechanics while(!openset.empty()) { - current = openset.front(); + current = openset.front(); // front has the lowest cost openset.pop_front(); if(current == goal) break; closedset.push_back(current); - for(int j = 0;j (mGraph[current].edges.size());j++) + // check all edges for the "current" point index + for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) { - //int next = mGraph[current].edges[j].destination if(std::find(closedset.begin(),closedset.end(),mGraph[current].edges[j].destination) == closedset.end()) { + // not in closedset - i.e. have not traversed this edge destination int dest = mGraph[current].edges[j].destination; float tentative_g = mGScore[current] + mGraph[current].edges[j].cost; bool isInOpenSet = std::find(openset.begin(),openset.end(),dest) != openset.end(); @@ -241,20 +216,22 @@ namespace MWMechanics mFScore[dest] = tentative_g + distance(pathGrid->mPoints[dest],pathGrid->mPoints[goal]); if(!isInOpenSet) { + // add this edge to openset, lowest cost goes to the front + // TODO: if this causes performance problems a hash table may help (apparently) std::list::iterator it = openset.begin(); for(it = openset.begin();it!= openset.end();it++) { - if(mGScore[*it]>mGScore[dest]) + if(mGScore[*it] > mGScore[dest]) break; } - openset.insert(it,dest); + openset.insert(it, dest); } } - } + } // if in closedset, i.e. traversed this edge already, try the next edge } - } + // reconstruct path to return std::list path; while(mGraph[current].parent != -1) { @@ -266,6 +243,9 @@ namespace MWMechanics current = mGraph[current].parent; } + // TODO: Is this a bug? If path is empty the destination is inserted. + // Commented out pending further testing. +#if 0 if(path.empty()) { ESM::Pathgrid::Point pt = pathGrid->mPoints[goal]; @@ -273,10 +253,19 @@ namespace MWMechanics pt.mY += yCell; path.push_front(pt); } - +#endif return path; } + /* + * NOTE: This method may fail to find a path. The caller must check the result before using it. + * If there is no path the AI routies need to implement some other heuristics to reach the target. + * + * Updates mPath using aStarSearch(). + * mPathConstructed is set true if successful, false if not + * + * May update mGraph by calling buildPathgridGraph() if it isn't constructed yet. + */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, const MWWorld::CellStore* cell, bool allowShortcuts) { @@ -310,11 +299,10 @@ namespace MWMechanics { if(!mIsGraphConstructed) buildPathgridGraph(pathGrid); - mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell);//findPath(startNode, endNode, mGraph); + mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell); if(!mPath.empty()) { - mPath.push_back(endPoint); mIsPathConstructed = true; } } @@ -331,8 +319,8 @@ namespace MWMechanics float PathFinder::getZAngleToNext(float x, float y) const { - // This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call - // if otherwise). + // This should never happen (programmers should have an if statement checking + // mIsPathConstructed that prevents this call if otherwise). if(mPath.empty()) return 0.; From 07fd801d94c14d24d0615da7263063d2401bdb63 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 29 Mar 2014 19:28:30 +1100 Subject: [PATCH 11/85] My previous analysis of the pathfinding issue was incorrect. It was in fact caused due to some of the pathgrid points being unreachable. Instead of returning an empty path in such a scenario, incorrect path + requested destination were being returned. There was also a defect where past cost was being used for selecting open points. There is still an unresolved issue where mGraph and mSCComp are being rebuilt unnecessarily. The check mCell != cell in buildPath() is being triggered frequently. Not sure why. --- apps/openmw/mwmechanics/aiwander.cpp | 26 +- apps/openmw/mwmechanics/aiwander.hpp | 2 + apps/openmw/mwmechanics/pathfinding.cpp | 393 +++++++++++++++++++----- apps/openmw/mwmechanics/pathfinding.hpp | 34 +- 4 files changed, 378 insertions(+), 77 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 26975ff1e..a3286b8c0 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -127,14 +127,18 @@ namespace MWMechanics mYCell = mCellY * ESM::Land::REAL_SIZE; } + // convert npcPos to local (i.e. cell) co-ordinates Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos); npcPos[0] = npcPos[0] - mXCell; npcPos[1] = npcPos[1] - mYCell; + // populate mAllowedNodes for this actor with pathgrid point indexes based on mDistance + // NOTE: mPoints and mAllowedNodes contain points in local co-ordinates for(unsigned int counter = 0; counter < mPathgrid->mPoints.size(); counter++) { - Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, mPathgrid->mPoints[counter].mY, - mPathgrid->mPoints[counter].mZ); + Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, + mPathgrid->mPoints[counter].mY, + mPathgrid->mPoints[counter].mZ); if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance) mAllowedNodes.push_back(mPathgrid->mPoints[counter]); } @@ -145,8 +149,9 @@ namespace MWMechanics unsigned int index = 0; for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) { - Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY, - mAllowedNodes[counterThree].mZ); + Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, + mAllowedNodes[counterThree].mY, + mAllowedNodes[counterThree].mZ); float tempDist = npcPos.squaredDistance(nodePos); if(tempDist < closestNode) index = counterThree; @@ -277,16 +282,24 @@ namespace MWMechanics dest.mY = destNodePos[1] + mYCell; dest.mZ = destNodePos[2]; + // actor position is already in world co-ordinates ESM::Pathgrid::Point start; start.mX = pos.pos[0]; start.mY = pos.pos[1]; start.mZ = pos.pos[2]; + // don't take shortcuts for wandering mPathFinder.buildPath(start, dest, actor.getCell(), false); if(mPathFinder.isPathConstructed()) { - // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): + // buildPath inserts dest in case it is not a pathgraph point index + // which is a duplicate for AiWander + //if(mPathFinder.getPathSize() > 1) + //mPathFinder.getPath().pop_back(); + + // Remove this node as an option and add back the previously used node + // (stops NPC from picking the same node): ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; mAllowedNodes.erase(mAllowedNodes.begin() + randNode); mAllowedNodes.push_back(mCurrentNode); @@ -377,7 +390,10 @@ namespace MWMechanics } else { + // normal walk forward actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + // turn towards the next point in mPath + // TODO: possibly no need to check every frame, maybe every 30 should be ok? zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 6de0b8181..48b67fb1c 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -38,8 +38,10 @@ namespace MWMechanics float mY; float mZ; + // Cell location int mCellX; int mCellY; + // Cell location multiply by ESM::Land::REAL_SIZE to get the right scale float mXCell; float mYCell; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index c0b78f3c7..0eb7f0798 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -37,19 +37,75 @@ namespace return sqrt(x * x + y * y + z * z); } - int getClosestPoint(const ESM::Pathgrid* grid, float x, float y, float z) + // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html + // + // One of the smallest cost in Seyda Neen is between points 77 & 78: + // pt x y + // 77 = 8026, 4480 + // 78 = 7986, 4218 + // + // Euclidean distance is about 262 (ignoring z) and Manhattan distance is 300 + // (again ignoring z). Using a value of about 300 for D seems like a reasonable + // starting point for experiments. If in doubt, just use value 1. + // + // The distance between 3 & 4 are pretty small, too. + // 3 = 5435, 223 + // 4 = 5948, 193 + // + // Approx. 514 Euclidean distance and 533 Manhattan distance. + // + float manhattan(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + { + return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ)); + } + + // Choose a heuristics - these may not be the best for directed graphs with + // non uniform edge costs. + // + // distance: + // - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2) + // - slower but more accurate + // + // Manhattan: + // - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z| + // - faster but not the shortest path + float costAStar(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + { + //return distance(a, b); + return manhattan(a, b); + } + + // Slightly cheaper version for comparisons. + // Caller needs to be careful for very short distances (i.e. less than 1) + // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 + // + float distanceSquared(ESM::Pathgrid::Point point, Ogre::Vector3 pos) + { + return Ogre::Vector3(point.mX, point.mY, point.mZ).squaredDistance(pos); + } + + // Return the closest pathgrid point index from the specified position co + // -ordinates. NOTE: Does not check if there is a sensible way to get there + // (e.g. a cliff in front). + // + // NOTE: pos is expected to be in local co-ordinates, as is grid->mPoints + // + int getClosestPoint(const ESM::Pathgrid* grid, Ogre::Vector3 pos) { if(!grid || grid->mPoints.empty()) return -1; - float distanceBetween = distance(grid->mPoints[0], x, y, z); + float distanceBetween = distanceSquared(grid->mPoints[0], pos); int closestIndex = 0; + // TODO: if this full scan causes performance problems mapping pathgrid + // points to a quadtree may help for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) { - if(distance(grid->mPoints[counter], x, y, z) < distanceBetween) + float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); + if(potentialDistBetween < distanceBetween) { - distanceBetween = distance(grid->mPoints[counter], x, y, z); + distanceBetween = potentialDistBetween; closestIndex = counter; } } @@ -57,6 +113,39 @@ namespace return closestIndex; } + // Uses mSCComp to choose a reachable end pathgrid point. start is assumed reachable. + std::pair getClosestReachablePoint(const ESM::Pathgrid* grid, + Ogre::Vector3 pos, int start, std::vector &sCComp) + { + // assume grid is fine + int startGroup = sCComp[start]; + + float distanceBetween = distanceSquared(grid->mPoints[0], pos); + int closestIndex = 0; + int closestReachableIndex = 0; + // TODO: if this full scan causes performance problems mapping pathgrid + // points to a quadtree may help + for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) + { + float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); + if(potentialDistBetween < distanceBetween) + { + // found a closer one + distanceBetween = potentialDistBetween; + closestIndex = counter; + if (sCComp[counter] == startGroup) + { + closestReachableIndex = counter; + } + } + } + if(start == closestReachableIndex) + closestReachableIndex = -1; // couldn't find anyting other than start + + return std::pair + (closestReachableIndex, closestReachableIndex == closestIndex); + } + } namespace MWMechanics @@ -76,13 +165,13 @@ namespace MWMechanics } /* - * NOTE: based on buildPath2(), please check git history if interested + * NOTE: Based on buildPath2(), please check git history if interested * - * Populate mGraph with the cost of each allowed edge (measured in distance ^2) - * Any existing data in mGraph is wiped clean first. The node's parent is - * set with initial value of -1. The parent values are populated by aStarSearch(). - * mGSore and mFScore are also resized. + * Populate mGraph with the cost of each allowed edge. * + * Any existing data in mGraph is wiped clean first. The node's parent + * is set with initial value of -1. The parent values are populated by + * aStarSearch() in order to reconstruct a path. * * mGraph[f].edges[n].destination = t * @@ -91,7 +180,7 @@ namespace MWMechanics * n = index of edges from point f * * - * Example: (note from p(0) to p(2) not allowed) + * Example: (note from p(0) to p(2) not allowed in this example) * * mGraph[0].edges[0].destination = 1 * .edges[1].destination = 3 @@ -130,25 +219,110 @@ namespace MWMechanics Node node; node.label = i; node.parent = -1; - mGraph[i] = node; // TODO: old code used push_back(node), check if any difference + mGraph[i] = node; } - // store the costs (measured in distance ^2) of each edge, in both directions + // store the costs of each edge for(unsigned int i = 0; i < pathGrid->mEdges.size(); i++) { Edge edge; - edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0], - pathGrid->mPoints[pathGrid->mEdges[i].mV1]); + edge.cost = costAStar(pathGrid->mPoints[pathGrid->mEdges[i].mV0], + pathGrid->mPoints[pathGrid->mEdges[i].mV1]); // forward path of the edge edge.destination = pathGrid->mEdges[i].mV1; mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge); // reverse path of the edge - // NOTE: These are redundant, ESM already contains the required reverse paths + // NOTE: These are redundant, the ESM already contains the reverse paths. //edge.destination = pathGrid->mEdges[i].mV0; //mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); } mIsGraphConstructed = true; } + // v is the pathgrid point index (some call them vertices) + void PathFinder::recursiveStrongConnect(int v) + { + mSCCPoint[v].first = mSCCIndex; // index + mSCCPoint[v].second = mSCCIndex; // lowlink + mSCCIndex++; + mSCCStack.push_back(v); + int w; + + for(int i = 0; i < mGraph[v].edges.size(); i++) + { + w = mGraph[v].edges[i].destination; + if(mSCCPoint[w].first == -1) // not visited + { + recursiveStrongConnect(w); // recurse + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].second); + } + else + { + if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].first); + } + } + + if(mSCCPoint[v].second == mSCCPoint[v].first) + { + // new component + do + { + w = mSCCStack.back(); + mSCCStack.pop_back(); + mSCComp[w] = mSCCId; + } + while(w != v); + + mSCCId++; + } + return; + } + + /* + * mSCComp contains the strongly connected component group id's. + * + * A cell can have disjointed pathgrid, e.g. Seyda Neen which has 3 + * + * mSCComp for Seyda Neen will have 3 different values. When selecting a + * random pathgrid point for AiWander, mSCComp can be checked for quickly + * finding whether the destination is reachable. + * + * Otherwise, buildPath will automatically select a closest reachable end + * pathgrid point (reachable from the closest start point). + * + * Using Tarjan's algorithm + * + * mGraph | graph G | + * mSCCPoint | V | derived from pathGrid->mPoints + * mGraph[v].edges | E (for v) | + * mSCCIndex | index | keep track of smallest unused index + * mSCCStack | S | + * pathGrid + * ->mEdges[v].mV1 | w | = mGraph[v].edges[i].destination + * + * FIXME: Some of these can be cleaned up by including them to struct + * Node used by mGraph + */ + void PathFinder::buildConnectedPoints(const ESM::Pathgrid* pathGrid) + { + mSCComp.clear(); + mSCComp.resize(pathGrid->mPoints.size(), 0); + mSCCId = 0; + + mSCCIndex = 0; + mSCCStack.clear(); + mSCCPoint.clear(); + mSCCPoint.resize(pathGrid->mPoints.size(), std::pair (-1, -1)); + + for(unsigned int v = 0; v < pathGrid->mPoints.size(); v++) + { + if(mSCCPoint[v].first == -1) // undefined (haven't visited) + recursiveStrongConnect(v); + } + } + void PathFinder::cleanUpAStar() { for(int i = 0; i < static_cast (mGraph.size()); i++) @@ -160,7 +334,8 @@ namespace MWMechanics } /* - * NOTE: based on buildPath2(), please check git history if interested + * NOTE: Based on buildPath2(), please check git history if interested + * Should consider a using 3rd party library version (e.g. boost) * * Find the shortest path to the target goal using a well known algorithm. * Uses mGraph which has pre-computed costs for allowed edges. It is assumed @@ -169,20 +344,27 @@ namespace MWMechanics * * Returns path (a list of pathgrid point indexes) which may be empty. * - * openset - point indexes to be traversed, lowest cost at the front - * closedset - point indexes already traversed + * Input params: + * start, goal - pathgrid point indexes (for this cell) + * xCell, yCell - values to add to convert path back to world scale * - * mGScore - past accumulated costs vector indexed by point index - * mFScore - future estimated costs vector indexed by point index - * these are resized by buildPathgridGraph() + * Variables: + * openset - point indexes to be traversed, lowest cost at the front + * closedset - point indexes already traversed * - * The heuristics used is distance^2 from current position to the final goal. + * Class variables: + * mGScore - past accumulated costs vector indexed by point index + * mFScore - future estimated costs vector indexed by point index + * these are resized by buildPathgridGraph() */ - std::list PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell, float yCell) + std::list PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid, + int start, int goal, + float xCell, float yCell) { cleanUpAStar(); + // mGScore & mFScore keep costs for each pathgrid point in pathGrid->mPoints mGScore[start] = 0; - mFScore[start] = distance(pathGrid->mPoints[start],pathGrid->mPoints[goal]); + mFScore[start] = costAStar(pathGrid->mPoints[start], pathGrid->mPoints[goal]); std::list openset; std::list closedset; @@ -195,33 +377,36 @@ namespace MWMechanics current = openset.front(); // front has the lowest cost openset.pop_front(); - if(current == goal) break; + if(current == goal) + break; - closedset.push_back(current); + closedset.push_back(current); // remember we've been here - // check all edges for the "current" point index + // check all edges for the current point index for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) { - if(std::find(closedset.begin(),closedset.end(),mGraph[current].edges[j].destination) == closedset.end()) + if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].destination) == + closedset.end()) { // not in closedset - i.e. have not traversed this edge destination int dest = mGraph[current].edges[j].destination; float tentative_g = mGScore[current] + mGraph[current].edges[j].cost; - bool isInOpenSet = std::find(openset.begin(),openset.end(),dest) != openset.end(); + bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); if(!isInOpenSet - || tentative_g < mGScore[dest] ) + || tentative_g < mGScore[dest]) { mGraph[dest].parent = current; mGScore[dest] = tentative_g; - mFScore[dest] = tentative_g + distance(pathGrid->mPoints[dest],pathGrid->mPoints[goal]); + mFScore[dest] = tentative_g + + costAStar(pathGrid->mPoints[dest], pathGrid->mPoints[goal]); if(!isInOpenSet) { // add this edge to openset, lowest cost goes to the front - // TODO: if this causes performance problems a hash table may help (apparently) + // TODO: if this causes performance problems a hash table may help std::list::iterator it = openset.begin(); - for(it = openset.begin();it!= openset.end();it++) + for(it = openset.begin(); it!= openset.end(); it++) { - if(mGScore[*it] > mGScore[dest]) + if(mFScore[*it] > mFScore[dest]) break; } openset.insert(it, dest); @@ -231,11 +416,14 @@ namespace MWMechanics } } - // reconstruct path to return std::list path; + if(current != goal) + return path; // for some reason couldn't build a path + // e.g. start was not reachable (we assume it is) + + // reconstruct path to return, using world co-ordinates while(mGraph[current].parent != -1) { - //std::cout << "not empty" << xCell; ESM::Pathgrid::Point pt = pathGrid->mPoints[current]; pt.mX += xCell; pt.mY += yCell; @@ -243,7 +431,8 @@ namespace MWMechanics current = mGraph[current].parent; } - // TODO: Is this a bug? If path is empty the destination is inserted. + // TODO: Is this a bug? If path is empty the algorithm couldn't find a path. + // Simply using the destination as the path in this scenario seems strange. // Commented out pending further testing. #if 0 if(path.empty()) @@ -258,63 +447,127 @@ namespace MWMechanics } /* - * NOTE: This method may fail to find a path. The caller must check the result before using it. - * If there is no path the AI routies need to implement some other heuristics to reach the target. + * NOTE: This method may fail to find a path. The caller must check the + * result before using it. If there is no path the AI routies need to + * implement some other heuristics to reach the target. + * + * NOTE: startPoint & endPoint are in world co-ordinates * - * Updates mPath using aStarSearch(). - * mPathConstructed is set true if successful, false if not + * Updates mPath using aStarSearch() or ray test (if shortcut allowed). + * mPath consists of pathgrid points, except the last element which is + * endPoint. This may be useful where the endPoint is not on a pathgrid + * point (e.g. combat). However, if the caller has already chosen a + * pathgrid point (e.g. wander) then it may be worth while to call + * pop_back() to remove the redundant entry. * - * May update mGraph by calling buildPathgridGraph() if it isn't constructed yet. + * mPathConstructed is set true if successful, false if not + * + * May update mGraph by calling buildPathgridGraph() if it isn't + * constructed yet. At the same time mConnectedPoints is also updated. + * + * NOTE: co-ordinates must be converted prior to calling getClosestPoint() + * + * | + * | cell + * | +-----------+ + * | | | + * | | | + * | | @ | + * | i | j | + * |<--->|<---->| | + * | +-----------+ + * | k + * |<---------->| world + * +----------------------------- + * + * i = x value of cell itself (multiply by ESM::Land::REAL_SIZE to convert) + * j = @.x in local co-ordinates (i.e. within the cell) + * k = @.x in world co-ordinates */ - void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, + void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, + const ESM::Pathgrid::Point &endPoint, const MWWorld::CellStore* cell, bool allowShortcuts) { mPath.clear(); - if(mCell != cell) mIsGraphConstructed = false; - mCell = cell; if(allowShortcuts) { - if(MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ, - endPoint.mX, endPoint.mY, endPoint.mZ)) - allowShortcuts = false; + // if there's a ray cast hit, can't take a direct path + if(!MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ, + endPoint.mX, endPoint.mY, endPoint.mZ)) + { + mPath.push_back(endPoint); + mIsPathConstructed = true; + return; + } + } + + if(mCell != cell) + { + mIsGraphConstructed = false; // must be in a new cell, need a new mGraph and mSCComp + mCell = cell; } - if(!allowShortcuts) + const ESM::Pathgrid *pathGrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); + float xCell = 0; + float yCell = 0; + + if (mCell->isExterior()) { - const ESM::Pathgrid *pathGrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); - float xCell = 0; - float yCell = 0; + xCell = mCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; + yCell = mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; + } - if (mCell->isExterior()) + // NOTE: It is possible that getClosestPoint returns a pathgrind point index + // that is unreachable in some situations. e.g. actor is standing + // outside an area enclosed by walls, but there is a pathgrid + // point right behind the wall that is closer than any pathgrid + // point outside the wall + // + // NOTE: getClosestPoint expects local co-ordinates + // + int startNode = getClosestPoint(pathGrid, + Ogre::Vector3(startPoint.mX - xCell, startPoint.mY - yCell, startPoint.mZ)); + + if(startNode != -1) // only check once, assume pathGrid won't change + { + if(!mIsGraphConstructed) { - xCell = mCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; - yCell = mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; + buildPathgridGraph(pathGrid); // pre-compute costs for use with aStarSearch + buildConnectedPoints(pathGrid); // must before calling getClosestReachablePoint } - int startNode = getClosestPoint(pathGrid, startPoint.mX - xCell, startPoint.mY - yCell,startPoint.mZ); - int endNode = getClosestPoint(pathGrid, endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ); + std::pair endNode = getClosestReachablePoint(pathGrid, + Ogre::Vector3(endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ), + startNode, mSCComp); - if(startNode != -1 && endNode != -1) + if(endNode.first != -1) { - if(!mIsGraphConstructed) buildPathgridGraph(pathGrid); - - mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell); + mPath = aStarSearch(pathGrid, startNode, endNode.first, xCell, yCell); if(!mPath.empty()) { mIsPathConstructed = true; + // Add the destination (which may be different to the closest + // pathgrid point). However only add if endNode was the closest + // point to endPoint. + // + // This logic can fail in the opposite situate, e.g. endPoint may + // have been reachable but happened to be very close to an + // unreachable pathgrid point. + // + // The AI routines will have to deal with such situations. + if(endNode.second) + mPath.push_back(endPoint); } + else + mIsPathConstructed = false; } + else + mIsPathConstructed = false; } else - { - mPath.push_back(endPoint); - mIsPathConstructed = true; - } - - if(mPath.empty()) - mIsPathConstructed = false; + mIsPathConstructed = false; // this shouldn't really happen, but just in case } float PathFinder::getZAngleToNext(float x, float y) const @@ -332,6 +585,7 @@ namespace MWMechanics return Ogre::Radian(Ogre::Math::ACos(directionY / directionResult) * sgn(Ogre::Math::ASin(directionX / directionResult))).valueDegrees(); } + // Used by AiCombat, use Euclidean distance float PathFinder::getDistToNext(float x, float y, float z) { ESM::Pathgrid::Point nextPoint = *mPath.begin(); @@ -372,6 +626,7 @@ namespace MWMechanics return false; } + // used by AiCombat, see header for the rationale void PathFinder::syncStart(const std::list &path) { if (mPath.size() < 2) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index ecaaef568..ae849bff2 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -64,9 +64,10 @@ namespace MWMechanics return mPath; } - //When first point of newly created path is the nearest to actor point, then - //the cituation can occure when this point is undesirable (if the 2nd point of new path == the 1st point of old path) - //This functions deletes that point. + // When first point of newly created path is the nearest to actor point, + // then a situation can occure when this point is undesirable + // (if the 2nd point of new path == the 1st point of old path) + // This functions deletes that point. void syncStart(const std::list &path); void addPointToPath(ESM::Pathgrid::Point &point) @@ -74,6 +75,13 @@ namespace MWMechanics mPath.push_back(point); } + // While a public method is defined here, it is anticipated that + // mSCComp will only be used internally. + std::vector getSCComp() const + { + return mSCComp; + } + private: struct Edge @@ -101,6 +109,26 @@ namespace MWMechanics std::list mPath; bool mIsGraphConstructed; const MWWorld::CellStore* mCell; + + // contains an integer indicating the groups of connected pathgrid points + // (all connected points will have the same value) + // + // In Seyda Neen there are 3: + // + // 52, 53 and 54 are one set (enclosed yard) + // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 are another (ship & office) + // all other pathgrid points are the third set + // + std::vector mSCComp; + // variables used to calculate mSCComp + int mSCCId; + int mSCCIndex; + std::list mSCCStack; + typedef std::pair VPair; // first is index, second is lowlink + std::vector mSCCPoint; + // methods used to calculate mSCComp + void recursiveStrongConnect(int v); + void buildConnectedPoints(const ESM::Pathgrid* pathGrid); }; } From ebb1813b9bb01ee3ae5f8c43e282ad6b93be3d6c Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 29 Mar 2014 20:17:04 +1100 Subject: [PATCH 12/85] Minor comment clarification. --- apps/openmw/mwmechanics/aiwander.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 48b67fb1c..ca6e546ed 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -41,7 +41,7 @@ namespace MWMechanics // Cell location int mCellX; int mCellY; - // Cell location multiply by ESM::Land::REAL_SIZE to get the right scale + // Cell location multiplied by ESM::Land::REAL_SIZE float mXCell; float mYCell; From c7b969821fd3e7ec2e8883dc289038e828a514d3 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 29 Mar 2014 11:11:43 +0100 Subject: [PATCH 13/85] silenced a warning --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 0eb7f0798..86f9f9af2 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -247,7 +247,7 @@ namespace MWMechanics mSCCStack.push_back(v); int w; - for(int i = 0; i < mGraph[v].edges.size(); i++) + for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) { w = mGraph[v].edges[i].destination; if(mSCCPoint[w].first == -1) // not visited From 33aecc521848ec949d6d5c00e831b9eb6d1ed5a4 Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Sat, 29 Mar 2014 07:45:36 -0400 Subject: [PATCH 14/85] Revert "made mReceiver private" This reverts commit 3e8f7b3bae9a3e23ad6953fbe79c199da79213aa. --- apps/openmw/mwgui/dialogue.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 368140520..242dae299 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -36,6 +36,7 @@ namespace MWGui virtual void open(); // The receiver of the bribe + MWWorld::Ptr mReceiver; void setReceiver(MWWorld::Ptr receiver); private: @@ -50,9 +51,6 @@ namespace MWGui void onCancel (MyGUI::Widget* sender); void onPersuade (MyGUI::Widget* sender); - - // The receiver of the bribe - MWWorld::Ptr mReceiver; }; From ad9286a30f906c373d453fcaab70abbd7b7f289a Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Sat, 29 Mar 2014 07:45:56 -0400 Subject: [PATCH 15/85] Revert "Revert "made mReceiver private"" This reverts commit 33aecc521848ec949d6d5c00e831b9eb6d1ed5a4. --- apps/openmw/mwgui/dialogue.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 242dae299..368140520 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -36,7 +36,6 @@ namespace MWGui virtual void open(); // The receiver of the bribe - MWWorld::Ptr mReceiver; void setReceiver(MWWorld::Ptr receiver); private: @@ -51,6 +50,9 @@ namespace MWGui void onCancel (MyGUI::Widget* sender); void onPersuade (MyGUI::Widget* sender); + + // The receiver of the bribe + MWWorld::Ptr mReceiver; }; From 6b28c06b2c6401d7a54a2df4e2f329b948acd7ed Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Sat, 29 Mar 2014 07:47:41 -0400 Subject: [PATCH 16/85] Feature #1233 Bribe gold in inventory - Corrected 1000 drake bribe --- apps/openmw/mwgui/dialogue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 6cc580fb6..e64c80c90 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -76,7 +76,7 @@ namespace MWGui else /*if (sender == mBribe1000Button)*/ { player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 1000, player); - mReceiver.getClass().getContainerStore(mReceiver).add(MWWorld::ContainerStore::sGoldId, 10000, mReceiver); + mReceiver.getClass().getContainerStore(mReceiver).add(MWWorld::ContainerStore::sGoldId, 1000, mReceiver); type = MWBase::MechanicsManager::PT_Bribe1000; } From 512ee1204ebf66795cfc64a7c070b80575d86157 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 29 Mar 2014 15:49:48 +0100 Subject: [PATCH 17/85] fixes a bug when resizing the window before a window listener is set --- libs/openengine/ogre/renderer.cpp | 17 ++++++++++++++++- libs/openengine/ogre/renderer.hpp | 9 ++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 5f9578988..caf62546e 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -157,6 +157,21 @@ void OgreRenderer::setFov(float fov) void OgreRenderer::windowResized(int x, int y) { - if (mWindowListener) + if (mWindowListener) { mWindowListener->windowResized(x,y); + } + else { + mWindowWidth = x; + mWindowHeight = y; + mOutstandingResize = true; + } +} + +void OgreRenderer::setWindowListener(WindowSizeListener* listener) +{ + mWindowListener = listener; + if (mOutstandingResize) { + windowResized(mWindowWidth, mWindowHeight); + mOutstandingResize = false; + } } diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index 767e7cf99..ad88b1606 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -66,6 +66,10 @@ namespace OEngine WindowSizeListener* mWindowListener; + int mWindowWidth; + int mWindowHeight; + bool mOutstandingResize; + public: OgreRenderer() : mRoot(NULL) @@ -77,6 +81,9 @@ namespace OEngine , mOgreInit(NULL) , mFader(NULL) , mWindowListener(NULL) + , mWindowWidth(0) + , mWindowHeight(0) + , mOutstandingResize(false) { } @@ -133,7 +140,7 @@ namespace OEngine /// Viewport Ogre::Viewport *getViewport() { return mView; } - void setWindowListener(WindowSizeListener* listener) { mWindowListener = listener; } + void setWindowListener(WindowSizeListener* listener); void adjustViewport(); }; From d9ea7107b7b619fcd17ba5702c5f57377b8f25eb Mon Sep 17 00:00:00 2001 From: gus Date: Sat, 29 Mar 2014 18:36:32 +0100 Subject: [PATCH 18/85] compile fix. --- apps/openmw/mwmechanics/aifollow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f85d8889b..c3b36516c 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -3,6 +3,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" #include "movement.hpp" #include From 72df9e77c6263b2341eaf9869a398005431c1868 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 28 Mar 2014 15:07:32 +0100 Subject: [PATCH 19/85] Don't show version text in the pause menu --- apps/openmw/mwgui/mainmenu.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 00e124f6c..5257baf22 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -174,6 +174,7 @@ namespace MWGui MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); showBackground(state == MWBase::StateManager::State_NoGame); + mVersionText->setVisible(state == MWBase::StateManager::State_NoGame); std::vector buttons; From 5eeed03f5b61645b5965586857418dff29f01ee7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 30 Mar 2014 00:12:31 +0100 Subject: [PATCH 20/85] Only exchange bribe gold if the bribe was accepted --- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 19 +++++++++++++++++- apps/openmw/mwgui/dialogue.cpp | 20 ------------------- apps/openmw/mwgui/dialogue.hpp | 6 ------ 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index c9e8ad955..88f1302bb 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -500,7 +500,24 @@ namespace MWDialogue mTemporaryDispositionChange = 100 - curDisp; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::Class::get(player).skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); + + if (success) + { + int gold=0; + if (type == MWBase::MechanicsManager::PT_Bribe10) + gold = 10; + else if (type == MWBase::MechanicsManager::PT_Bribe100) + gold = 100; + else if (type == MWBase::MechanicsManager::PT_Bribe1000) + gold = 1000; + + if (gold) + { + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, gold, player); + mActor.getClass().getContainerStore(mActor).add(MWWorld::ContainerStore::sGoldId, gold, mActor); + } + } std::string text; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index e64c80c90..6c43f47b4 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -56,29 +56,16 @@ namespace MWGui void PersuasionDialog::onPersuade(MyGUI::Widget *sender) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWBase::MechanicsManager::PersuasionType type; if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire; else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate; else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt; else if (sender == mBribe10Button) - { - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 10, player); - mReceiver.getClass().getContainerStore(mReceiver).add(MWWorld::ContainerStore::sGoldId, 10, mReceiver); type = MWBase::MechanicsManager::PT_Bribe10; - } else if (sender == mBribe100Button) - { - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 100, player); - mReceiver.getClass().getContainerStore(mReceiver).add(MWWorld::ContainerStore::sGoldId, 100, mReceiver); type = MWBase::MechanicsManager::PT_Bribe100; - } else /*if (sender == mBribe1000Button)*/ - { - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 1000, player); - mReceiver.getClass().getContainerStore(mReceiver).add(MWWorld::ContainerStore::sGoldId, 1000, mReceiver); type = MWBase::MechanicsManager::PT_Bribe1000; - } MWBase::Environment::get().getDialogueManager()->persuade(type); @@ -100,12 +87,6 @@ namespace MWGui mGoldLabel->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); } - // The receiver of the bribe - void PersuasionDialog::setReceiver(MWWorld::Ptr receiver) - { - mReceiver = receiver; - } - // -------------------------------------------------------------------------------------------------- Response::Response(const std::string &text, const std::string &title) @@ -380,7 +361,6 @@ namespace MWGui mPtr = actor; mTopicsList->setEnabled(true); setTitle(npcName); - mPersuasionDialog.setReceiver(mPtr); mTopicsList->clear(); diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 368140520..befbd6eee 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -35,9 +35,6 @@ namespace MWGui virtual void open(); - // The receiver of the bribe - void setReceiver(MWWorld::Ptr receiver); - private: MyGUI::Button* mCancelButton; MyGUI::Button* mAdmireButton; @@ -50,9 +47,6 @@ namespace MWGui void onCancel (MyGUI::Widget* sender); void onPersuade (MyGUI::Widget* sender); - - // The receiver of the bribe - MWWorld::Ptr mReceiver; }; From 6c866deb1b0123c8f620448fa07cdfbb904910b8 Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Sat, 29 Mar 2014 21:23:34 -0400 Subject: [PATCH 21/85] Feature #1154 Not all NPCs get aggressive when one is attacked Partially implemented --- apps/openmw/mwbase/mechanicsmanager.hpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 65 ++++++++++++------- .../mwmechanics/mechanicsmanagerimp.hpp | 2 +- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 22dda0ce0..36b8df519 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -111,7 +111,7 @@ namespace MWBase * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. * @return was the crime reported? */ - virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, + virtual bool commitCrime (const MWWorld::Ptr& offender, const MWWorld::Ptr& victim, OffenseType type, int arg=0) = 0; virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0) = 0; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 4c8f35edb..2c51e0083 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -3,6 +3,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -12,6 +13,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "aicombat.hpp" + #include #include "spellcasting.hpp" @@ -798,37 +801,55 @@ namespace MWMechanics return; commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); } - - bool MechanicsManager::commitCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) + + bool MechanicsManager::commitCrime(const MWWorld::Ptr& offender, const MWWorld::Ptr& victim, OffenseType type, int arg) { - if (ptr.getRefData().getHandle() != "player") + if (offender.getRefData().getHandle() != "player") return false; + MWWorld::Ptr ptr; + bool reported=false; - for (Actors::PtrControllerMap::const_iterator it = mActors.begin(); it != mActors.end(); ++it) + MWWorld::CellStore* cell = victim.getCell(); + + // TODO: implement and check the distance of actors to victim using fAlarmRadius + // get all NPCs in victims cell + for (MWWorld::CellRefList::List::iterator it (cell->get().mList.begin()); it != cell->get().mList.end(); ++it) { - if (it->first != ptr && - MWBase::Environment::get().getWorld()->getLOS(ptr, it->first) && - awarenessCheck(ptr, it->first)) + MWWorld::Ptr ptr (&*it, cell); + + // offender can't be ally to themselves + if (ptr == offender) + continue; + + CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); + + // curse at the thief + if (ptr == victim && type == OT_Theft) + MWBase::Environment::get().getDialogueManager()->say(victim, "Thief"); + + // TODO: Make guards persue unless other factors, such as bounty stops them + // If the actor is a guard + + // TODO: An actor reacts differently based on different values of AI_Alarm. + // Actor has witnessed a crime. Will he report it? + if (creatureStats.getAiSetting(CreatureStats::AI_Alarm).getModified() > 0) { - // 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"); - } + creatureStats.setAlarmed(true); + reported=true; + } + else + continue; - // 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) - { - // TODO: stats.setAlarmed(true) on NPCs within earshot - // fAlarmRadius ? - reported=true; - break; - } + // TODO: An actor reacts differently based on different values of AI_Fight and AI_Flee. + // Actor has reported the crime, will the actor fight the offender? + if (creatureStats.getAiSetting(CreatureStats::AI_Fight).getModified > 0) + { + creatureStats.getAiSequence().stack(AiCombat(offender)); + creatureStats.setHostile(true); + creatureStats.getAiSequence().execute(ptr, 0); } } - if (reported) reportCrime(ptr, victim, type, arg); return reported; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 761caf586..3327cf8b8 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -111,7 +111,7 @@ namespace MWMechanics * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. * @return was the crime reported? */ - virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, + virtual bool commitCrime (const MWWorld::Ptr& offender, const MWWorld::Ptr& victim, OffenseType type, int arg=0); virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0); From 98fd381564309f4d1cd15423821b2f2e8d65a00c Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Sat, 29 Mar 2014 21:25:20 -0400 Subject: [PATCH 22/85] Feature #1154 Not all NPCs get aggressive when one is attacked Compiling fix --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 2c51e0083..534fc941b 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -843,7 +843,7 @@ namespace MWMechanics // TODO: An actor reacts differently based on different values of AI_Fight and AI_Flee. // Actor has reported the crime, will the actor fight the offender? - if (creatureStats.getAiSetting(CreatureStats::AI_Fight).getModified > 0) + if (creatureStats.getAiSetting(CreatureStats::AI_Fight).getModified() > 0) { creatureStats.getAiSequence().stack(AiCombat(offender)); creatureStats.setHostile(true); From 8ce938c6f1537c63fb5a230a1188dbc3d42c8fdd Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Sat, 29 Mar 2014 22:26:53 -0400 Subject: [PATCH 23/85] Revert 6b28c06..98fd381 This rolls back to commit 6b28c06b2c6401d7a54a2df4e2f329b948acd7ed. --- apps/openmw/mwbase/mechanicsmanager.hpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 65 +++++++------------ .../mwmechanics/mechanicsmanagerimp.hpp | 2 +- 3 files changed, 24 insertions(+), 45 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 36b8df519..22dda0ce0 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -111,7 +111,7 @@ namespace MWBase * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. * @return was the crime reported? */ - virtual bool commitCrime (const MWWorld::Ptr& offender, const MWWorld::Ptr& victim, + virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0) = 0; virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0) = 0; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 534fc941b..4c8f35edb 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -3,7 +3,6 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/cellstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -13,8 +12,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" -#include "aicombat.hpp" - #include #include "spellcasting.hpp" @@ -801,55 +798,37 @@ namespace MWMechanics return; commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); } - - bool MechanicsManager::commitCrime(const MWWorld::Ptr& offender, const MWWorld::Ptr& victim, OffenseType type, int arg) + + bool MechanicsManager::commitCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) { - if (offender.getRefData().getHandle() != "player") + if (ptr.getRefData().getHandle() != "player") return false; - MWWorld::Ptr ptr; - bool reported=false; - MWWorld::CellStore* cell = victim.getCell(); - - // TODO: implement and check the distance of actors to victim using fAlarmRadius - // get all NPCs in victims cell - for (MWWorld::CellRefList::List::iterator it (cell->get().mList.begin()); it != cell->get().mList.end(); ++it) + for (Actors::PtrControllerMap::const_iterator it = mActors.begin(); it != mActors.end(); ++it) { - MWWorld::Ptr ptr (&*it, cell); - - // offender can't be ally to themselves - if (ptr == offender) - continue; - - CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); - - // curse at the thief - if (ptr == victim && type == OT_Theft) - MWBase::Environment::get().getDialogueManager()->say(victim, "Thief"); - - // TODO: Make guards persue unless other factors, such as bounty stops them - // If the actor is a guard - - // TODO: An actor reacts differently based on different values of AI_Alarm. - // Actor has witnessed a crime. Will he report it? - if (creatureStats.getAiSetting(CreatureStats::AI_Alarm).getModified() > 0) + if (it->first != ptr && + MWBase::Environment::get().getWorld()->getLOS(ptr, it->first) && + awarenessCheck(ptr, it->first)) { - creatureStats.setAlarmed(true); - reported=true; - } - else - continue; + // 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"); + } - // TODO: An actor reacts differently based on different values of AI_Fight and AI_Flee. - // Actor has reported the crime, will the actor fight the offender? - if (creatureStats.getAiSetting(CreatureStats::AI_Fight).getModified() > 0) - { - creatureStats.getAiSequence().stack(AiCombat(offender)); - creatureStats.setHostile(true); - creatureStats.getAiSequence().execute(ptr, 0); + // 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) + { + // TODO: stats.setAlarmed(true) on NPCs within earshot + // fAlarmRadius ? + reported=true; + break; + } } } + if (reported) reportCrime(ptr, victim, type, arg); return reported; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 3327cf8b8..761caf586 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -111,7 +111,7 @@ namespace MWMechanics * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. * @return was the crime reported? */ - virtual bool commitCrime (const MWWorld::Ptr& offender, const MWWorld::Ptr& victim, + virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0); virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0); From d04bb3befb996d9e04a5a9646ab3033657bc029d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 30 Mar 2014 17:23:22 +1100 Subject: [PATCH 24/85] MSVC uses C version of locale. --- apps/openmw/mwgui/savegamedialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index caa082646..d17cd8c21 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -259,7 +259,7 @@ namespace MWGui timeinfo = localtime(&time); // Use system/environment locale settings for datetime formatting - std::setlocale(LC_TIME, ""); + setlocale(LC_TIME, ""); const int size=1024; char buffer[size]; From 50af9bc0d356aea7941e6e26ed8a4e47c66eaac6 Mon Sep 17 00:00:00 2001 From: megaton <9megaton6@gmail.com> Date: Sun, 30 Mar 2014 19:01:59 +0400 Subject: [PATCH 25/85] General perfomance optimizations. --- apps/openmw/mwmechanics/actors.cpp | 18 +++++++++------ apps/openmw/mwrender/localmap.cpp | 13 +++++++---- components/nifogre/controller.hpp | 34 ++++++++++++++++++---------- components/nifogre/ogrenifloader.cpp | 17 +++++++++----- 4 files changed, 53 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 92be89f2f..270753718 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -196,9 +196,8 @@ namespace MWMechanics { 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 ) + + if( (fight == 100 ) || (fight >= 95 && d <= 3000) || (fight >= 90 && d <= 2000) || (fight >= 80 && d <= 1000) @@ -206,12 +205,17 @@ namespace MWMechanics || (fight >= 70 && disp <= 35 && d <= 1000) || (fight >= 60 && disp <= 30 && d <= 1000) || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) ) - && LOS + || (fight >= 40 && disp <= 10 && d <= 500) ) { - creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr())); - creatureStats.setHostile(true); + bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr); + + if (LOS) + { + creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr())); + creatureStats.setHostile(true); + } } } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 003f08300..0f6d782a6 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -390,8 +390,13 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog"); if (!tex.isNull()) { + std::map >::iterator anIter; + // get its buffer - if (mBuffers.find(texName) == mBuffers.end()) return; + anIter = mBuffers.find(texName); + if (anIter == mBuffers.end()) return; + + std::vector& aBuffer = (*anIter).second; int i=0; for (int texV = 0; texV> 24); alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); - mBuffers[texName][i] = (uint32) (alpha << 24); + aBuffer[i] = (uint32) (alpha << 24); ++i; } } // copy to the texture - memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &mBuffers[texName][0], sFogOfWarResolution*sFogOfWarResolution*4); + memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &aBuffer[0], sFogOfWarResolution*sFogOfWarResolution*4); tex->getBuffer()->unlock(); } } diff --git a/components/nifogre/controller.hpp b/components/nifogre/controller.hpp index 6d7f6ab3f..317447d95 100644 --- a/components/nifogre/controller.hpp +++ b/components/nifogre/controller.hpp @@ -18,16 +18,21 @@ namespace NifOgre if(time <= keys.front().mTime) return keys.front().mValue; - Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) + const Nif::FloatKey* keyArray = keys.data(); + size_t size = keys.size(); + + for (size_t i = 1; i < size; ++i) { - if(iter->mTime < time) + const Nif::FloatKey* aKey = &keyArray[i]; + + if(aKey->mTime < time) continue; - Nif::FloatKeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); + const Nif::FloatKey* aLastKey = &keyArray[i-1]; + float a = (time - aLastKey->mTime) / (aKey->mTime - aLastKey->mTime); + return aLastKey->mValue + ((aKey->mValue - aLastKey->mValue) * a); } + return keys.back().mValue; } @@ -36,16 +41,21 @@ namespace NifOgre if(time <= keys.front().mTime) return keys.front().mValue; - Nif::Vector3KeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) + const Nif::Vector3Key* keyArray = keys.data(); + size_t size = keys.size(); + + for (size_t i = 1; i < size; ++i) { - if(iter->mTime < time) + const Nif::Vector3Key* aKey = &keyArray[i]; + + if(aKey->mTime < time) continue; - Nif::Vector3KeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); + const Nif::Vector3Key* aLastKey = &keyArray[i-1]; + float a = (time - aLastKey->mTime) / (aKey->mTime - aLastKey->mTime); + return aLastKey->mValue + ((aKey->mValue - aLastKey->mValue) * a); } + return keys.back().mValue; } }; diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index acab419b0..ce8244619 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -385,16 +385,21 @@ public: if(time <= keys.front().mTime) return keys.front().mValue; - Nif::QuaternionKeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) + const Nif::QuaternionKey* keyArray = keys.data(); + size_t size = keys.size(); + + for (size_t i = 1; i < size; ++i) { - if(iter->mTime < time) + const Nif::QuaternionKey* aKey = &keyArray[i]; + + if(aKey->mTime < time) continue; - Nif::QuaternionKeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return Ogre::Quaternion::nlerp(a, last->mValue, iter->mValue); + const Nif::QuaternionKey* aLastKey = &keyArray[i-1]; + float a = (time - aLastKey->mTime) / (aKey->mTime - aLastKey->mTime); + return Ogre::Quaternion::nlerp(a, aLastKey->mValue, aKey->mValue); } + return keys.back().mValue; } From 126513120395cbe172f9b6ac961ec6c64a0062b9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 30 Mar 2014 20:07:43 +0200 Subject: [PATCH 26/85] Set the selected index after all items are added (workaround for MyGUI bug) --- apps/openmw/mwgui/savegamedialog.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index caa082646..52f6080d1 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -80,6 +80,8 @@ namespace MWGui mCharacterSelection->removeAllItems(); + int selectedIndex = MyGUI::ITEM_NONE; + for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it) { if (it->begin()!=it->end()) @@ -109,11 +111,13 @@ namespace MWGui it->begin()->mPath.parent_path().filename().string()))) { mCurrentCharacter = &*it; - mCharacterSelection->setIndexSelected(mCharacterSelection->getItemCount()-1); + selectedIndex = mCharacterSelection->getItemCount()-1; } } } + mCharacterSelection->setIndexSelected(selectedIndex); + fillSaveList(); } From f5810b8e1c7416dc5d71cef2ec99cd8fdb70da65 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 30 Mar 2014 23:04:12 +0200 Subject: [PATCH 27/85] Consider aspect ratio for loading screen background More consistent with the main menu. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwgui/backgroundimage.cpp | 63 ++++++++++++++++++++++++ apps/openmw/mwgui/backgroundimage.hpp | 37 ++++++++++++++ apps/openmw/mwgui/loadingscreen.cpp | 47 +++++++----------- apps/openmw/mwgui/loadingscreen.hpp | 9 ++-- apps/openmw/mwgui/mainmenu.cpp | 34 +++---------- apps/openmw/mwgui/mainmenu.hpp | 6 +-- apps/openmw/mwgui/windowmanagerimp.cpp | 2 + files/mygui/openmw_loading_screen.layout | 14 ++---- 9 files changed, 143 insertions(+), 71 deletions(-) create mode 100644 apps/openmw/mwgui/backgroundimage.cpp create mode 100644 apps/openmw/mwgui/backgroundimage.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 511435108..d1f7c45f3 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -33,7 +33,7 @@ add_openmw_dir (mwgui merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog - recharge mode videowidget + recharge mode videowidget backgroundimage ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/mwgui/backgroundimage.cpp b/apps/openmw/mwgui/backgroundimage.cpp new file mode 100644 index 000000000..1e87c0ff1 --- /dev/null +++ b/apps/openmw/mwgui/backgroundimage.cpp @@ -0,0 +1,63 @@ +#include "backgroundimage.hpp" + +#include + +namespace MWGui +{ + +void BackgroundImage::setBackgroundImage (const std::string& image, bool fixedRatio, bool correct) +{ + if (mChild) + { + MyGUI::Gui::getInstance().destroyWidget(mChild); + mChild = NULL; + } + if (correct) + { + setImageTexture("black.png"); + + if (fixedRatio) + mAspect = 4.0/3.0; + else + mAspect = 0; // TODO + + mChild = createWidgetReal("ImageBox", + MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); + mChild->setImageTexture(image); + + adjustSize(); + } + else + { + mAspect = 0; + setImageTexture(image); + } +} + +void BackgroundImage::adjustSize() +{ + if (mAspect == 0) + return; + + MyGUI::IntSize screenSize = getSize(); + + int leftPadding = std::max(0.0, (screenSize.width - screenSize.height * mAspect) / 2); + int topPadding = std::max(0.0, (screenSize.height - screenSize.width / mAspect) / 2); + + mChild->setCoord(leftPadding, topPadding, screenSize.width - leftPadding*2, screenSize.height - topPadding*2); +} + +void BackgroundImage::setSize (const MyGUI::IntSize& _value) +{ + MyGUI::Widget::setSize (_value); + adjustSize(); +} + +void BackgroundImage::setCoord (const MyGUI::IntCoord& _value) +{ + MyGUI::Widget::setCoord (_value); + adjustSize(); +} + + +} diff --git a/apps/openmw/mwgui/backgroundimage.hpp b/apps/openmw/mwgui/backgroundimage.hpp new file mode 100644 index 000000000..3d1a61eaf --- /dev/null +++ b/apps/openmw/mwgui/backgroundimage.hpp @@ -0,0 +1,37 @@ +#ifndef OPENMW_MWGUI_BACKGROUNDIMAGE_H +#define OPENMW_MWGUI_BACKGROUNDIMAGE_H + +#include + +namespace MWGui +{ + + /** + * @brief A variant of MyGUI::ImageBox with aspect ratio correction using black bars + */ + class BackgroundImage : public MyGUI::ImageBox + { + MYGUI_RTTI_DERIVED(BackgroundImage) + + public: + BackgroundImage() : mChild(NULL), mAspect(0) {} + + /** + * @param fixedRatio Use a fixed ratio of 4:3, regardless of the image dimensions + * @param correct Add black bars? + */ + void setBackgroundImage (const std::string& image, bool fixedRatio=true, bool correct=true); + + virtual void setSize (const MyGUI::IntSize &_value); + virtual void setCoord (const MyGUI::IntCoord &_value); + + private: + MyGUI::ImageBox* mChild; + double mAspect; + + void adjustSize(); + }; + +} + +#endif diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index b3f70a5ab..7917c75f3 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -9,14 +9,14 @@ #include #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/inputmanager.hpp" +#include "backgroundimage.hpp" + namespace MWGui { @@ -32,28 +32,13 @@ namespace MWGui { getWidget(mLoadingText, "LoadingText"); getWidget(mProgressBar, "ProgressBar"); - getWidget(mBackgroundImage, "BackgroundImage"); mProgressBar->setScrollViewPage(1); - mBackgroundMaterial = Ogre::MaterialManager::getSingleton().create("BackgroundMaterial", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - mBackgroundMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); - mBackgroundMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); - mBackgroundMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); - - mRectangle = new Ogre::Rectangle2D(true); - mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0); - mRectangle->setMaterial("BackgroundMaterial"); - // Render the background before everything else - mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY-1); - // Use infinite AAB to always stay visible - Ogre::AxisAlignedBox aabInf; - aabInf.setInfinite(); - mRectangle->setBoundingBox(aabInf); - // Attach background to the scene - Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - node->attachObject(mRectangle); - mRectangle->setVisible(false); + mBackgroundImage = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, + MyGUI::Align::Stretch, "Menu"); + + setVisible(false); } void LoadingScreen::setLabel(const std::string &label) @@ -63,18 +48,25 @@ namespace MWGui LoadingScreen::~LoadingScreen() { - delete mRectangle; + } + + void LoadingScreen::setVisible(bool visible) + { + WindowBase::setVisible(visible); + mBackgroundImage->setVisible(visible); } void LoadingScreen::onResChange(int w, int h) { setCoord(0,0,w,h); + + mBackgroundImage->setCoord(MyGUI::IntCoord(0,0,w,h)); } void LoadingScreen::loadingOn() { // Early-out if already on - if (mRectangle->getVisible()) + if (mMainWidget->getVisible()) return; // Temporarily turn off VSync, we want to do actual loading rather than waiting for the screen to sync. @@ -106,7 +98,7 @@ namespace MWGui texture->createInternalResources(); mWindow->copyContentsToMemory(texture->getBuffer()->lock(Ogre::Image::Box(0,0,width,height), Ogre::HardwareBuffer::HBL_DISCARD)); texture->getBuffer()->unlock(); - mBackgroundImage->setImageTexture(texture->getName()); + mBackgroundImage->setBackgroundImage(texture->getName(), false, false); } setVisible(true); @@ -149,9 +141,10 @@ namespace MWGui { std::string const & randomSplash = mResources.at (rand() % mResources.size()); - Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); + Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); - mBackgroundImage->setImageTexture (randomSplash); + // TODO: add option (filename pattern?) to use image aspect ratio instead of 4:3 + mBackgroundImage->setBackgroundImage(randomSplash, true, true); } else std::cerr << "No loading screens found!" << std::endl; @@ -237,8 +230,6 @@ namespace MWGui mWindow->update(false); - mRectangle->setVisible(false); - // resume 3d rendering mSceneMgr->clearSpecialCaseRenderQueues(); mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index e91e5951d..55235173f 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -10,6 +10,8 @@ namespace MWGui { + class BackgroundImage; + class LoadingScreen : public WindowBase, public Loading::Listener { public: @@ -25,6 +27,8 @@ namespace MWGui virtual void setProgress (size_t value); virtual void increaseProgress (size_t increase); + virtual void setVisible(bool visible); + virtual void removeWallpaper(); LoadingScreen(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* rw); @@ -51,10 +55,7 @@ namespace MWGui MyGUI::TextBox* mLoadingText; MyGUI::ScrollBar* mProgressBar; - MyGUI::ImageBox* mBackgroundImage; - - Ogre::Rectangle2D* mRectangle; - Ogre::MaterialPtr mBackgroundMaterial; + BackgroundImage* mBackgroundImage; Ogre::StringVector mResources; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 5257baf22..b6e3915bb 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -14,6 +14,8 @@ #include "savegamedialog.hpp" #include "confirmationdialog.hpp" +#include "imagebutton.hpp" +#include "backgroundimage.hpp" namespace MWGui { @@ -132,34 +134,14 @@ namespace MWGui void MainMenu::showBackground(bool show) { - if (mBackground) - { - MyGUI::Gui::getInstance().destroyWidget(mBackground); - mBackground = NULL; - } - if (show) + if (show && !mBackground) { - if (!mBackground) - { - mBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Stretch, "Menu"); - mBackground->setImageTexture("black.png"); - - // Use black bars to correct aspect ratio. The video player also does it, so we need to do it - // for mw_logo.bik to align correctly with menu_morrowind.dds. - MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); - - // No way to un-hardcode this right now, menu_morrowind.dds is 1024x512 but was designed for 4:3 - double imageaspect = 4.0/3.0; - - int leftPadding = std::max(0.0, (screenSize.width - screenSize.height * imageaspect) / 2); - int topPadding = std::max(0.0, (screenSize.height - screenSize.width / imageaspect) / 2); - - MyGUI::ImageBox* image = mBackground->createWidget("ImageBox", - leftPadding, topPadding, screenSize.width - leftPadding*2, screenSize.height - topPadding*2, MyGUI::Align::Default); - image->setImageTexture("textures\\menu_morrowind.dds"); - } + mBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, + MyGUI::Align::Stretch, "Menu"); + mBackground->setBackgroundImage("textures\\menu_morrowind.dds"); } + if (mBackground) + mBackground->setVisible(show); } void MainMenu::updateMenu() diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index c571fda86..c27442536 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -3,11 +3,11 @@ #include -#include "imagebutton.hpp" - namespace MWGui { + class ImageButton; + class BackgroundImage; class SaveGameDialog; class MainMenu : public OEngine::GUI::Layout @@ -29,7 +29,7 @@ namespace MWGui MyGUI::Widget* mButtonBox; MyGUI::TextBox* mVersionText; - MyGUI::ImageBox* mBackground; + BackgroundImage* mBackground; std::map mButtons; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1e019aaa9..db19070a6 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -61,6 +61,7 @@ #include "itemview.hpp" #include "fontloader.hpp" #include "videowidget.hpp" +#include "backgroundimage.hpp" namespace MWGui { @@ -160,6 +161,7 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); BookPage::registerMyGUIComponents (); ItemView::registerComponents(); diff --git a/files/mygui/openmw_loading_screen.layout b/files/mygui/openmw_loading_screen.layout index 5fd3440f9..19649cfd2 100644 --- a/files/mygui/openmw_loading_screen.layout +++ b/files/mygui/openmw_loading_screen.layout @@ -4,17 +4,13 @@ - + - - - - - - - - + + + + From db4975dab8908d7cff82551060e4da10430871dd Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Mon, 31 Mar 2014 21:52:20 +0200 Subject: [PATCH 28/85] Changed IRC notifications for Travis CI from normal messages to IRC notices. Signed-off-by: Lukasz Gromanowski --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5d0326a07..e09fa46dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,4 +41,4 @@ notifications: - "chat.freenode.net#openmw" on_success: change on_failure: always - + use_notice: true From 529d2436b50d0eecba4cd489e3767da02f11394c Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Mon, 31 Mar 2014 22:14:12 +0200 Subject: [PATCH 29/85] Temporary added broken CMakeLists.txt - Travis IRC notification test. Signed-off-by: Lukasz Gromanowski --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 392fdfc66..0b5b0f8d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,7 +116,7 @@ if (WIN32) endif() # We probably support older versions than this. -cmake_minimum_required(VERSION 2.6) +cmake_minimum_required(VERSION 999.6) # source directory: libs From 5b5069535eff4df7d97eafe9912235dd35823bad Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 1 Apr 2014 10:04:14 +0200 Subject: [PATCH 30/85] keep track of active cells in PagedWorldspaceWidget and update SubView title accordingly --- .../view/render/pagedworldspacewidget.cpp | 37 ++++++++++++++++++- .../view/render/pagedworldspacewidget.hpp | 14 +++++++ apps/opencs/view/render/worldspacewidget.cpp | 2 + apps/opencs/view/render/worldspacewidget.hpp | 3 ++ apps/opencs/view/world/scenesubview.cpp | 30 ++++++++++++++- apps/opencs/view/world/scenesubview.hpp | 4 ++ 6 files changed, 87 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index fa32e3959..96d44543e 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -1,6 +1,39 @@ #include "pagedworldspacewidget.hpp" +#include + CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget *parent) -: WorldspaceWidget (parent) -{} \ No newline at end of file +: WorldspaceWidget (parent), mMin (std::make_pair (0, 0)), mMax (std::make_pair (-1, -1)) +{} + +void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) +{ + if (!hint.empty()) + { + if (hint[0]=='c') + { + char ignore1, ignore2, ignore3; + std::pair cellIndex; + + std::istringstream stream (hint.c_str()); + if (stream >> ignore1 >> ignore2 >> ignore3 >> cellIndex.first >> cellIndex.second) + { + setCellIndex (cellIndex, cellIndex); + + /// \todo adjust camera position + } + } + + /// \todo implement 'r' type hints + } +} + +void CSVRender::PagedWorldspaceWidget::setCellIndex (const std::pair& min, + const std::pair& max) +{ + mMin = min; + mMax = max; + + emit cellIndexChanged (mMin, mMax); +} \ No newline at end of file diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 172e2477a..9a4b79c3a 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -9,9 +9,23 @@ namespace CSVRender { Q_OBJECT + std::pair mMin; + std::pair mMax; + public: PagedWorldspaceWidget (QWidget *parent); + ///< \note Sets the cell area selection to an invalid value to indicate that currently + /// no cells are displayed. The cells to be displayed will be specified later through + /// hint system. + + virtual void useViewHint (const std::string& hint); + + void setCellIndex (const std::pair& min, const std::pair& max); + + signals: + + void cellIndexChanged (const std::pair& min, const std::pair& max); }; } diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 9959c5a67..4d2442c89 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -26,6 +26,8 @@ void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) setNavigation (&mOrbit); } +void CSVRender::WorldspaceWidget::useViewHint (const std::string& hint) {} + void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() { setNavigation (&m1st); diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 7921c3560..f7208d7a1 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -33,6 +33,9 @@ namespace CSVRender void selectDefaultNavigationMode(); + virtual void useViewHint (const std::string& hint); + ///< Default-implementation: ignored. + private slots: void selectNavigationMode (const std::string& mode); diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 10e8b4071..c075cb4d6 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -1,6 +1,8 @@ #include "scenesubview.hpp" +#include + #include #include #include @@ -35,7 +37,14 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolbar *toolbar = new SceneToolbar (48, this); if (id.getId()=="sys::default") - mScene = new CSVRender::PagedWorldspaceWidget (this); + { + CSVRender::PagedWorldspaceWidget *widget = new CSVRender::PagedWorldspaceWidget (this); + mScene = widget; + connect (widget, + SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), + this, + SLOT (cellIndexChanged (const std::pair&, const std::pair&))); + } else mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); @@ -83,7 +92,26 @@ void CSVWorld::SceneSubView::setStatusBar (bool show) mBottom->setStatusBar (show); } +void CSVWorld::SceneSubView::useHint (const std::string& hint) +{ + mScene->useViewHint (hint); +} + void CSVWorld::SceneSubView::closeRequest() { deleteLater(); +} + +void CSVWorld::SceneSubView::cellIndexChanged (const std::pair& min, + const std::pair& max) +{ + std::ostringstream stream; + stream << "Scene: " << getUniversalId().getId() << " (" << min.first << ", " << min.second; + + if (min!=max) + stream << " to " << max.first << ", " << max.second; + + stream << ")"; + + setWindowTitle (QString::fromUtf8 (stream.str().c_str())); } \ No newline at end of file diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index ecf3fe4e4..ee5b7b41f 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -38,9 +38,13 @@ namespace CSVWorld virtual void setStatusBar (bool show); + virtual void useHint (const std::string& hint); + private slots: void closeRequest(); + + void cellIndexChanged (const std::pair& min, const std::pair& max); }; } From 78d322e6a5f3a5921776221c2f9ce2c6b878f4cc Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 1 Apr 2014 11:44:27 +0200 Subject: [PATCH 31/85] Revert "Temporary added broken CMakeLists.txt - Travis IRC notification test." This reverts commit 529d2436b50d0eecba4cd489e3767da02f11394c. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b5b0f8d5..392fdfc66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,7 +116,7 @@ if (WIN32) endif() # We probably support older versions than this. -cmake_minimum_required(VERSION 999.6) +cmake_minimum_required(VERSION 2.6) # source directory: libs From 6ee998ac38d9c387f9507831a9dd11d418e86d85 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 1 Apr 2014 18:44:55 +0200 Subject: [PATCH 32/85] updated credits file --- credits.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/credits.txt b/credits.txt index cd533de3a..eb427a22b 100644 --- a/credits.txt +++ b/credits.txt @@ -52,6 +52,7 @@ Marc Bouvier (CramitDeFrog) Marcin Hulist (Gohan) Mark Siewert (mark76) Mateusz Kołaczek (PL_kolek) +megaton Michael Hogan (Xethik) Michael Mc Donnell Michael Papageorgiou (werdanith) From 4037f3705e8855933a04a76b1bc717840ac99fd7 Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Tue, 1 Apr 2014 14:15:55 -0400 Subject: [PATCH 33/85] 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 | 148 ++++++++++++------ apps/openmw/mwscript/aiextensions.cpp | 2 +- apps/openmw/mwworld/class.cpp | 5 + apps/openmw/mwworld/class.hpp | 2 + 9 files changed, 139 insertions(+), 61 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)) + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + + // 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) ) { - // 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"); - } + // Say something! + // TODO: Add more messages + if (type == OT_Theft) + MWBase::Environment::get().getDialogueManager()->say(*it, "Thief"); - // 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) + // 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; }; } From 50dac98a2befb0722a043c5f29fd73f1eaec440e Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Tue, 1 Apr 2014 20:24:25 -0400 Subject: [PATCH 34/85] Feature 1154 & 73: Crime and NPC reactions --- apps/openmw/mwbase/world.hpp | 2 + apps/openmw/mwclass/npc.cpp | 6 ++ apps/openmw/mwmechanics/aiactivate.cpp | 6 +- apps/openmw/mwmechanics/creaturestats.cpp | 14 ++++ apps/openmw/mwmechanics/creaturestats.hpp | 6 ++ .../mwmechanics/mechanicsmanagerimp.cpp | 69 ++++++++----------- apps/openmw/mwworld/worldimp.cpp | 16 +++++ apps/openmw/mwworld/worldimp.hpp | 1 + 8 files changed, 78 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index bb6f5741d..170e6705a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -513,6 +513,8 @@ namespace MWBase virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0; + + virtual void resetCrimes(const MWWorld::Ptr& ptr) = 0; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4b84bd03e..5a3ff10de 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) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index b3fe40e1f..472bca88f 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -9,6 +9,7 @@ #include "steering.hpp" #include "movement.hpp" +#include "creaturestats.hpp" MWMechanics::AiActivate::AiActivate(const std::string &objectId, int arg) : mObjectId(objectId), @@ -26,9 +27,8 @@ 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); + if (mArg == 1) // run to actor + actor.getClass().getCreatureStats(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/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index feed8d182..cb1d8e275 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -17,6 +17,7 @@ namespace MWMechanics mAttacked (false), mHostile (false), mAttackingOrSpell(false), mIsWerewolf(false), + mWitnesses(), mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f) { @@ -496,4 +497,17 @@ namespace MWMechanics { return mGoldPool; } + + void CreatureStats::addPlayerWitnesses(std::vector witnesses) + { + mWitnesses.insert(mWitnesses.end(), witnesses.begin(), witnesses.end()); + } + std::vector CreatureStats::getPlayerWitnesses() const + { + return mWitnesses; + } + void CreatureStats::resetPlayerWitnesses() + { + mWitnesses.clear(); + } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 20a9a5799..527adf8c0 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -59,6 +59,8 @@ namespace MWMechanics int mGoldPool; // the pool of merchant gold not in inventory + std::vector mWitnesses; // the witnesses to players crimes + protected: bool mIsWerewolf; AttributeValue mWerewolfAttributes[8]; @@ -233,6 +235,10 @@ namespace MWMechanics void setGoldPool(int pool); int getGoldPool() const; + + void addPlayerWitnesses(std::vector witnesses); + std::vector getPlayerWitnesses() const; + void resetPlayerWitnesses(); }; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b7ff10381..4d1fca72d 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -826,6 +826,11 @@ namespace MWMechanics else if (type == OT_Theft) alarm = esmStore.get().find("iAlarmStealing")->getInt(); + // what is the bounty cutoff? To high the guards will attack + float cutoff = float(esmStore.get().find("iCrimeThreshold")->getInt()) * + float(esmStore.get().find("iCrimeThresholdMultiplier")->getInt()) * + esmStore.get().find("fCrimeGoldDiscountMult")->getFloat(); + // Innocent until proven guilty bool reported = false; @@ -842,7 +847,7 @@ namespace MWMechanics CreatureStats& creatureStats = MWWorld::Class::get(*it).getCreatureStats(*it); - // Did the witness see the crime? + // Did a witness see the crime? if ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) { // Say something! @@ -853,65 +858,51 @@ namespace MWMechanics // Will the witness report the crime? if (creatureStats.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { - 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); - } + reportCrime(ptr, victim, type, arg); // 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) + if (*it1 == ptr) // Not the player continue; - // Is it a guard? Or will the witness fight? + // was the witness alarmed? CreatureStats& creatureStats1 = MWWorld::Class::get(*it1).getCreatureStats(*it1); + if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) + creatureStats1.setAlarmed(true); + + // was the witness a guard? 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; + // will the guard try to kill the player? + if (ptr.getClass().getNpcStats(ptr).getBounty() >= cutoff) + { + creatureStats1.getAiSequence().stack(AiCombat(ptr)); + creatureStats1.setHostile(true); + creatureStats1.getAiSequence().execute(*it,0); + } + else // will the guard persue the player? + { + creatureStats1.getAiSequence().stack(AiActivate(ptr.getClass().getId(ptr), 1)); + creatureStats1.getAiSequence().execute(*it,0); + } } - else if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) + // will the witness fight the player? + else if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm || + type == OT_Assault) { 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) + ptr.getClass().getCreatureStats(ptr).addPlayerWitnesses(neighbors); return reported; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 81afc394a..3c53cabee 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2762,12 +2762,28 @@ namespace MWWorld message += "\n" + skillMsg; } + resetCrimes(player); + std::vector buttons; buttons.push_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); } } + void World::resetCrimes(const MWWorld::Ptr& ptr) + { + // Reset witnesses to the players crimes + std::vector neighbors = ptr.getClass().getCreatureStats(ptr).getPlayerWitnesses(); + for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) + { + // This to reset for each witness: + // TODO: More research is needed to complete this list + it->getClass().getCreatureStats(*it).setHostile(false); + it->getClass().getCreatureStats(*it).setAlarmed(false); + } + ptr.getClass().getCreatureStats(ptr).resetPlayerWitnesses(); + } + void World::spawnRandomCreature(const std::string &creatureList) { const ESM::CreatureLevList* list = getStore().get().find(creatureList); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 42f52cb61..06d49ad24 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -611,6 +611,7 @@ namespace MWWorld virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName); + virtual void resetCrimes(const MWWorld::Ptr& ptr); }; } From 7c0b51fb7e6a3757c3fbe4047b88826512bc0897 Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Wed, 2 Apr 2014 00:18:22 -0400 Subject: [PATCH 35/85] Ai pursue now controls guards pursuit of crimes Should extend AiActivate in the future --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwmechanics/aiactivate.cpp | 9 +- apps/openmw/mwmechanics/aiactivate.hpp | 3 +- apps/openmw/mwmechanics/aipackage.hpp | 3 +- apps/openmw/mwmechanics/aipersue.cpp | 108 ++++++++++++++++++ apps/openmw/mwmechanics/aipersue.hpp | 29 +++++ apps/openmw/mwmechanics/aisequence.cpp | 11 +- apps/openmw/mwmechanics/aisequence.hpp | 3 + .../mwmechanics/mechanicsmanagerimp.cpp | 9 +- apps/openmw/mwscript/aiextensions.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 11 +- 11 files changed, 171 insertions(+), 19 deletions(-) create mode 100644 apps/openmw/mwmechanics/aipersue.cpp create mode 100644 apps/openmw/mwmechanics/aipersue.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 20011b0d9..f7977dc59 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 security spellsuccess spellcasting disease pickpocket levelledlist combat steering ) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 472bca88f..8610cf4b2 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -9,11 +9,9 @@ #include "steering.hpp" #include "movement.hpp" -#include "creaturestats.hpp" -MWMechanics::AiActivate::AiActivate(const std::string &objectId, int arg) - : mObjectId(objectId), - mArg(arg) +MWMechanics::AiActivate::AiActivate(const std::string &objectId) + : mObjectId(objectId) { } MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const @@ -27,9 +25,6 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) Movement &movement = actor.getClass().getMovementSettings(actor); const ESM::Cell *cell = actor.getCell()->getCell(); - if (mArg == 1) // run to actor - actor.getClass().getCreatureStats(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 aa8725db2..fd54869f6 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -12,7 +12,7 @@ namespace MWMechanics class AiActivate : public AiPackage { public: - AiActivate(const std::string &objectId, int arg); + AiActivate(const std::string &objectId); virtual AiActivate *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? @@ -20,7 +20,6 @@ namespace MWMechanics private: std::string mObjectId; - int mArg; PathFinder mPathFinder; int mCellX; diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 74c77bf97..8e015da15 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 000000000..21239860f --- /dev/null +++ b/apps/openmw/mwmechanics/aipersue.cpp @@ -0,0 +1,108 @@ +#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) +{ + //TODO: Guards should not dialague with player after crime reset + + 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 000000000..3fd708ab3 --- /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 ba69b8098..c67367a6c 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; @@ -161,7 +170,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(), 0); + package = new MWMechanics::AiActivate(data.mName.toString()); } else //if (it->mType == ESM::AI_Follow) { diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 62f48f981..07b7c898c 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/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 4d1fca72d..6b9d6902e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -13,6 +13,7 @@ #include "../mwworld/player.hpp" #include "aicombat.hpp" +#include "aipersue.hpp" #include "aiactivate.hpp" #include @@ -838,8 +839,6 @@ namespace MWMechanics 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 @@ -880,12 +879,12 @@ namespace MWMechanics { creatureStats1.getAiSequence().stack(AiCombat(ptr)); creatureStats1.setHostile(true); - creatureStats1.getAiSequence().execute(*it,0); + creatureStats1.getAiSequence().execute(*it1,0); } else // will the guard persue the player? { - creatureStats1.getAiSequence().stack(AiActivate(ptr.getClass().getId(ptr), 1)); - creatureStats1.getAiSequence().execute(*it,0); + creatureStats1.getAiSequence().stack(AiPersue(ptr.getClass().getId(ptr))); + creatureStats1.getAiSequence().execute(*it1,0); } } // will the witness fight the player? diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 898788bf1..8314d011a 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 buttons; @@ -2776,10 +2780,15 @@ namespace MWWorld std::vector neighbors = ptr.getClass().getCreatureStats(ptr).getPlayerWitnesses(); for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { - // This to reset for each witness: + // Reset states // TODO: More research is needed to complete this list it->getClass().getCreatureStats(*it).setHostile(false); it->getClass().getCreatureStats(*it).setAlarmed(false); + + // Stop guard persue + if(it->getClass().isClass(*it, "Guard")) + it->getClass().getCreatureStats(*it).getAiSequence().stopPersue(); + } ptr.getClass().getCreatureStats(ptr).resetPlayerWitnesses(); } From 6f1211dd8d8140585a20d24693645561fa4611cc Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Wed, 2 Apr 2014 12:23:38 -0400 Subject: [PATCH 36/85] Moved mWitnesses into Player. resetCrime for paying fine. --- apps/openmw/mwmechanics/creaturestats.cpp | 14 -------------- apps/openmw/mwmechanics/creaturestats.hpp | 6 ------ apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwscript/miscextensions.cpp | 6 +++++- apps/openmw/mwworld/player.cpp | 13 +++++++++++++ apps/openmw/mwworld/player.hpp | 6 ++++++ apps/openmw/mwworld/worldimp.cpp | 4 ++-- 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index cb1d8e275..feed8d182 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -17,7 +17,6 @@ namespace MWMechanics mAttacked (false), mHostile (false), mAttackingOrSpell(false), mIsWerewolf(false), - mWitnesses(), mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f) { @@ -497,17 +496,4 @@ namespace MWMechanics { return mGoldPool; } - - void CreatureStats::addPlayerWitnesses(std::vector witnesses) - { - mWitnesses.insert(mWitnesses.end(), witnesses.begin(), witnesses.end()); - } - std::vector CreatureStats::getPlayerWitnesses() const - { - return mWitnesses; - } - void CreatureStats::resetPlayerWitnesses() - { - mWitnesses.clear(); - } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 527adf8c0..20a9a5799 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -59,8 +59,6 @@ namespace MWMechanics int mGoldPool; // the pool of merchant gold not in inventory - std::vector mWitnesses; // the witnesses to players crimes - protected: bool mIsWerewolf; AttributeValue mWerewolfAttributes[8]; @@ -235,10 +233,6 @@ namespace MWMechanics void setGoldPool(int pool); int getGoldPool() const; - - void addPlayerWitnesses(std::vector witnesses); - std::vector getPlayerWitnesses() const; - void resetPlayerWitnesses(); }; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 6b9d6902e..8714e3eaf 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -901,7 +901,7 @@ namespace MWMechanics } } if (reported) - ptr.getClass().getCreatureStats(ptr).addPlayerWitnesses(neighbors); + MWBase::Environment::get().getWorld()->getPlayer().addPlayerWitnesses(neighbors); return reported; } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index fa8441aa5..781a34368 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -810,9 +810,11 @@ namespace MWScript public: virtual void execute(Interpreter::Runtime &runtime) { + MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty(0); - MWBase::Environment::get().getWorld()->confiscateStolenItems(player); + world->confiscateStolenItems(player); + world->resetCrimes(player); } }; @@ -821,8 +823,10 @@ namespace MWScript public: virtual void execute(Interpreter::Runtime &runtime) { + MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty(0); + world->resetCrimes(player); } }; diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 6d551ecf1..7db2afb3f 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -274,4 +274,17 @@ namespace MWWorld return false; } + + void Player::addPlayerWitnesses(std::vector witnesses) + { + mWitnesses.insert(mWitnesses.end(), witnesses.begin(), witnesses.end()); + } + std::vector Player::getPlayerWitnesses() const + { + return mWitnesses; + } + void Player::resetPlayerWitnesses() + { + mWitnesses.clear(); + } } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 7eb023a2b..001d3b7a6 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -41,6 +41,8 @@ namespace MWWorld bool mAutoMove; int mForwardBackward; bool mTeleported; + + std::vector mWitnesses; public: Player(const ESM::NPC *player, const MWBase::World& world); @@ -94,6 +96,10 @@ namespace MWWorld void write (ESM::ESMWriter& writer) const; bool readRecord (ESM::ESMReader& reader, int32_t type); + + void addPlayerWitnesses(std::vector witnesses); + std::vector getPlayerWitnesses() const; + void resetPlayerWitnesses(); }; } #endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 726176c07..848b57b54 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2777,7 +2777,7 @@ namespace MWWorld void World::resetCrimes(const MWWorld::Ptr& ptr) { // Reset witnesses to the players crimes - std::vector neighbors = ptr.getClass().getCreatureStats(ptr).getPlayerWitnesses(); + std::vector neighbors = mPlayer->getPlayerWitnesses(); for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { // Reset states @@ -2790,7 +2790,7 @@ namespace MWWorld it->getClass().getCreatureStats(*it).getAiSequence().stopPersue(); } - ptr.getClass().getCreatureStats(ptr).resetPlayerWitnesses(); + mPlayer->resetPlayerWitnesses(); } void World::spawnRandomCreature(const std::string &creatureList) From 510f2d10ac38f16c0fc0d9846a2ecdd2a9212df9 Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Wed, 2 Apr 2014 14:20:33 -0400 Subject: [PATCH 37/85] Replaces broken code with todo --- apps/openmw/mwworld/worldimp.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 848b57b54..954b8e126 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2763,8 +2763,7 @@ namespace MWWorld message += "\n" + skillMsg; } - // sleep the player, this doesn't work - //player.getClass().calculateRestoration(player, days, true); + // TODO: Sleep the player resetCrimes(player); From f597d3e88bc85208681b74d5c72579a6e81488d3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 3 Apr 2014 07:46:26 +1100 Subject: [PATCH 38/85] Use duration rather than frame counts. Stops false detection of being "stuck" with high frame rates (e.g. indoors). --- apps/openmw/mwmechanics/aiwander.cpp | 43 +++++++++++++++++----------- apps/openmw/mwmechanics/aiwander.hpp | 3 +- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index a3286b8c0..e89d43ca4 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -18,9 +18,10 @@ namespace MWMechanics { // NOTE: determined empirically but probably need further tweaking - static const int COUNT_BEFORE_STUCK = 20; static const int COUNT_BEFORE_RESET = 200; - static const int COUNT_EVADE = 7; + static const float DIST_SAME_SPOT = 1.8f; + static const float DURATION_SAME_SPOT = 1.0f; + static const float DURATION_TO_EVADE = 0.4f; AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) @@ -35,7 +36,8 @@ namespace MWMechanics , mPrevY(0) , mWalkState(State_Norm) , mStuckCount(0) - , mEvadeCount(0) + , mEvadeDuration(0) + , mStuckDuration(0) , mSaidGreeting(false) { for(unsigned short counter = 0; counter < mIdle.size(); counter++) @@ -325,15 +327,21 @@ namespace MWMechanics } else { - /* 1 n + /* f t * State_Norm <---> State_CheckStuck --> State_Evade * ^ ^ | ^ | ^ | | * | | | | | | | | - * | +---+ +---+ +---+ | m - * | any < n < m | + * | +---+ +---+ +---+ | u + * | any < t < u | * +--------------------------------------------+ + * + * f = one frame + * t = how long before considered stuck + * u = how long to move sideways */ - bool samePosition = (abs(pos.pos[0] - mPrevX) < 1) && (abs(pos.pos[1] - mPrevY) < 1); + bool samePosition = (abs(pos.pos[0] - mPrevX) < DIST_SAME_SPOT) && + (abs(pos.pos[1] - mPrevY) < DIST_SAME_SPOT); + switch(mWalkState) { case State_Norm: @@ -349,30 +357,33 @@ namespace MWMechanics if(!samePosition) { mWalkState = State_Norm; - // to do this properly need yet another variable, simply don't clear for now - //mStuckCount = 0; + mStuckDuration = 0; break; } else { - // consider stuck only if position unchanges consecutively - if((mStuckCount++ % COUNT_BEFORE_STUCK) == 0) + mStuckDuration += duration; + // consider stuck only if position unchanges for a period + if(mStuckDuration > DURATION_SAME_SPOT) + { mWalkState = State_Evade; - // NOTE: mStuckCount is purposely not cleared here + mStuckDuration = 0; + mStuckCount++; + } else - break; // still in the same state, but counter got incremented + break; // still in the same state, but duration added to timer } } /* FALL THROUGH */ case State_Evade: { - if(mEvadeCount++ < COUNT_EVADE) + mEvadeDuration += duration; + if(mEvadeDuration < DURATION_TO_EVADE) break; else { mWalkState = State_Norm; // tried to evade, assume all is ok and start again - // NOTE: mStuckCount is purposely not cleared here - mEvadeCount = 0; + mEvadeDuration = 0; } } /* NO DEFAULT CASE */ diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index ca6e546ed..75621911e 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -58,7 +58,8 @@ namespace MWMechanics WalkState mWalkState; int mStuckCount; - int mEvadeCount; + float mStuckDuration; + float mEvadeDuration; bool mStoredAvailableNodes; bool mChooseAction; From 58b135a2be7d23f77192b2471702fd4ec960310f Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Thu, 3 Apr 2014 00:50:09 -0400 Subject: [PATCH 39/85] Crime is now checked every frame call --- apps/openmw/mwbase/world.hpp | 2 - apps/openmw/mwmechanics/actors.cpp | 53 +++++++++++++++++-- apps/openmw/mwmechanics/actors.hpp | 2 + apps/openmw/mwmechanics/aipersue.cpp | 4 +- apps/openmw/mwmechanics/creaturestats.cpp | 12 ++++- apps/openmw/mwmechanics/creaturestats.hpp | 5 ++ .../mwmechanics/mechanicsmanagerimp.cpp | 47 +++------------- apps/openmw/mwscript/miscextensions.cpp | 5 -- apps/openmw/mwworld/player.cpp | 13 ----- apps/openmw/mwworld/player.hpp | 4 -- apps/openmw/mwworld/worldimp.cpp | 21 -------- apps/openmw/mwworld/worldimp.hpp | 1 - 12 files changed, 76 insertions(+), 93 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 170e6705a..bb6f5741d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -513,8 +513,6 @@ namespace MWBase virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0; - - virtual void resetCrimes(const MWWorld::Ptr& ptr) = 0; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 92be89f2f..11469c1a9 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -29,6 +29,9 @@ #include "aicombat.hpp" #include "aifollow.hpp" +#include "aipersue.hpp" + +#include "../mwbase/dialoguemanager.hpp" //------------------------ namespace { @@ -175,14 +178,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(); @@ -214,7 +219,7 @@ namespace MWMechanics creatureStats.setHostile(true); } } - + updateCrimePersuit(ptr, duration); creatureStats.getAiSequence().execute (ptr,duration); } @@ -711,6 +716,48 @@ namespace MWMechanics } } + void Actors::updateCrimePersuit(const MWWorld::Ptr& ptr, float duration) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + int bounty = player.getClass().getNpcStats(player).getBounty(); + + // 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(); + + if (ptr != player) + { + CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); + // Alarmed or not, I will kill you because you've commited heinous against the empire + if ((!creatureStats.isAlarmed() || creatureStats.isAlarmed()) && + ptr.getClass().isClass(ptr, "Guard") && bounty >= cutoff && !creatureStats.isHostile()) + creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player))); + else if (creatureStats.isAlarmed()) + { + MWBase::Environment::get().getDialogueManager()->say(ptr, "Thief"); + if(bounty == 0) + { + creatureStats.setAlarmed(false); + creatureStats.setHostile(false); + if (ptr.getClass().isClass(ptr, "Guard")) + creatureStats.getAiSequence().stopPersue(); + creatureStats.getAiSequence().stopCombat(); + + } + + if (ptr.getClass().isClass(ptr, "Guard") && !creatureStats.isHostile()) + creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player))); + else if (!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 4b18ac862..d61d74258 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/aipersue.cpp b/apps/openmw/mwmechanics/aipersue.cpp index 21239860f..36e18946c 100644 --- a/apps/openmw/mwmechanics/aipersue.cpp +++ b/apps/openmw/mwmechanics/aipersue.cpp @@ -19,10 +19,8 @@ MWMechanics::AiPersue *MWMechanics::AiPersue::clone() const { return new AiPersue(*this); } -bool MWMechanics::AiPersue::execute (const MWWorld::Ptr& actor,float duration) +bool MWMechanics::AiPersue::execute (const MWWorld::Ptr& actor, float duration) { - //TODO: Guards should not dialague with player after crime reset - MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::Position pos = actor.getRefData().getPosition(); Movement &movement = actor.getClass().getMovementSettings(actor); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index feed8d182..a358917de 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -14,7 +14,7 @@ namespace MWMechanics CreatureStats::CreatureStats() : mLevel (0), mDead (false), mDied (false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), - mAttacked (false), mHostile (false), + mAttacked (false), mHostile (false), mAssaulted(false), mAttackingOrSpell(false), mIsWerewolf(false), mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false), @@ -316,6 +316,16 @@ namespace MWMechanics mHostile = hostile; } + bool CreatureStats::isAssaulted() const + { + return mAssaulted; + } + + void CreatureStats::setAssaulted (bool assaulted) + { + mAssaulted = assaulted; + } + bool CreatureStats::getCreatureTargetted() const { std::string target; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 20a9a5799..7db895dab 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -39,6 +39,7 @@ namespace MWMechanics bool mAlarmed; bool mAttacked; bool mHostile; + bool mAssaulted; bool mAttackingOrSpell; bool mKnockdown; bool mHitRecovery; @@ -186,6 +187,10 @@ namespace MWMechanics void setHostile (bool hostile); + bool isAssaulted() const; + + void setAssaulted (bool assaulted); + bool getCreatureTargetted() const; float getEvasion() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 8714e3eaf..2edb3d177 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" @@ -12,10 +13,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" -#include "aicombat.hpp" -#include "aipersue.hpp" -#include "aiactivate.hpp" - #include #include "spellcasting.hpp" @@ -827,11 +824,6 @@ namespace MWMechanics else if (type == OT_Theft) alarm = esmStore.get().find("iAlarmStealing")->getInt(); - // what is the bounty cutoff? To high the guards will attack - float cutoff = float(esmStore.get().find("iCrimeThreshold")->getInt()) * - float(esmStore.get().find("iCrimeThresholdMultiplier")->getInt()) * - esmStore.get().find("fCrimeGoldDiscountMult")->getFloat(); - // Innocent until proven guilty bool reported = false; @@ -846,8 +838,9 @@ namespace MWMechanics CreatureStats& creatureStats = MWWorld::Class::get(*it).getCreatureStats(*it); - // Did a witness see the crime? - if ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) + // Was the crime seen or the victim assulted? + if ( ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) || + type == OT_Assault) { // Say something! // TODO: Add more messages @@ -858,7 +851,6 @@ namespace MWMechanics if (creatureStats.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { reported = true; - reportCrime(ptr, victim, type, arg); // Tell everyone else for (std::vector::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1) @@ -870,38 +862,13 @@ namespace MWMechanics CreatureStats& creatureStats1 = MWWorld::Class::get(*it1).getCreatureStats(*it1); if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) creatureStats1.setAlarmed(true); - - // was the witness a guard? - if (it1->getClass().isClass(*it1, "Guard")) - { - // will the guard try to kill the player? - if (ptr.getClass().getNpcStats(ptr).getBounty() >= cutoff) - { - creatureStats1.getAiSequence().stack(AiCombat(ptr)); - creatureStats1.setHostile(true); - creatureStats1.getAiSequence().execute(*it1,0); - } - else // will the guard persue the player? - { - creatureStats1.getAiSequence().stack(AiPersue(ptr.getClass().getId(ptr))); - creatureStats1.getAiSequence().execute(*it1,0); - } - } - // will the witness fight the player? - else if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm || - type == OT_Assault) - { - creatureStats1.getAiSequence().stack(AiCombat(ptr)); - creatureStats1.setHostile(true); - creatureStats1.getAiSequence().execute(*it1,0); - } } break; // Someone saw the crime and everyone has been told - } + } } } - if (reported) - MWBase::Environment::get().getWorld()->getPlayer().addPlayerWitnesses(neighbors); + if(reported) + reportCrime(ptr, victim, type, arg); return reported; } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 781a34368..cd29e2c91 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -810,11 +810,8 @@ namespace MWScript public: virtual void execute(Interpreter::Runtime &runtime) { - MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty(0); - world->confiscateStolenItems(player); - world->resetCrimes(player); } }; @@ -823,10 +820,8 @@ namespace MWScript public: virtual void execute(Interpreter::Runtime &runtime) { - MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty(0); - world->resetCrimes(player); } }; diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 7db2afb3f..6d551ecf1 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -274,17 +274,4 @@ namespace MWWorld return false; } - - void Player::addPlayerWitnesses(std::vector witnesses) - { - mWitnesses.insert(mWitnesses.end(), witnesses.begin(), witnesses.end()); - } - std::vector Player::getPlayerWitnesses() const - { - return mWitnesses; - } - void Player::resetPlayerWitnesses() - { - mWitnesses.clear(); - } } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 001d3b7a6..b41e6fc9f 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -96,10 +96,6 @@ namespace MWWorld void write (ESM::ESMWriter& writer) const; bool readRecord (ESM::ESMReader& reader, int32_t type); - - void addPlayerWitnesses(std::vector witnesses); - std::vector getPlayerWitnesses() const; - void resetPlayerWitnesses(); }; } #endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 954b8e126..f3e404c35 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2765,33 +2765,12 @@ namespace MWWorld // TODO: Sleep the player - resetCrimes(player); - std::vector buttons; buttons.push_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); } } - void World::resetCrimes(const MWWorld::Ptr& ptr) - { - // Reset witnesses to the players crimes - std::vector neighbors = mPlayer->getPlayerWitnesses(); - for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) - { - // Reset states - // TODO: More research is needed to complete this list - it->getClass().getCreatureStats(*it).setHostile(false); - it->getClass().getCreatureStats(*it).setAlarmed(false); - - // Stop guard persue - if(it->getClass().isClass(*it, "Guard")) - it->getClass().getCreatureStats(*it).getAiSequence().stopPersue(); - - } - mPlayer->resetPlayerWitnesses(); - } - void World::spawnRandomCreature(const std::string &creatureList) { const ESM::CreatureLevList* list = getStore().get().find(creatureList); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 06d49ad24..42f52cb61 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -611,7 +611,6 @@ namespace MWWorld virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName); - virtual void resetCrimes(const MWWorld::Ptr& ptr); }; } From b1abef7a38a9e21b2fcffcf3473829dfbbb8149c Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Thu, 3 Apr 2014 01:07:56 -0400 Subject: [PATCH 40/85] Cleaned up code --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwscript/miscextensions.cpp | 1 + apps/openmw/mwworld/player.hpp | 3 +-- apps/openmw/mwworld/worldimp.cpp | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 2edb3d177..39cf63cd0 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -867,7 +867,7 @@ namespace MWMechanics } } } - if(reported) + if (reported) reportCrime(ptr, victim, type, arg); return reported; } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index cd29e2c91..a7f31c80b 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -822,6 +822,7 @@ namespace MWScript { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty(0); + MWBase::Environment::get().getWorld()->confiscateStolenItems(player); } }; diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index b41e6fc9f..9d3fbbeec 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -41,8 +41,7 @@ namespace MWWorld bool mAutoMove; int mForwardBackward; bool mTeleported; - - std::vector mWitnesses; + public: Player(const ESM::NPC *player, const MWBase::World& world); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f3e404c35..ac6f9d380 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -29,7 +29,6 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/levelledlist.hpp" #include "../mwmechanics/combat.hpp" -#include "../mwmechanics/actors.hpp" #include "../mwrender/sky.hpp" #include "../mwrender/animation.hpp" From 98f77714ce928de922ec6b83ccf667500f1d8e1e Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 3 Apr 2014 21:41:34 +1100 Subject: [PATCH 41/85] Per-cell pathgrid data and calculation moved off PathFinder. Now the edge cost calculations and strongly connected component searches are done only once per cell. Per-actor data and methods still remain with PathFinder. This version still has debugging statements and needs cleaning up. --- apps/openmw/mwmechanics/pathfinding.cpp | 726 +++++++++++++----------- apps/openmw/mwmechanics/pathfinding.hpp | 82 +-- apps/openmw/mwworld/cellstore.cpp | 30 + apps/openmw/mwworld/cellstore.hpp | 9 + 4 files changed, 494 insertions(+), 353 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 86f9f9af2..f56cfad5f 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -54,13 +54,13 @@ namespace // // Approx. 514 Euclidean distance and 533 Manhattan distance. // - float manhattan(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + float manhattan(const ESM::Pathgrid::Point a, const ESM::Pathgrid::Point b) { return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ)); } - // Choose a heuristics - these may not be the best for directed graphs with - // non uniform edge costs. + // Choose a heuristics - Note that these may not be the best for directed + // graphs with non-uniform edge costs. // // distance: // - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2) @@ -69,7 +69,7 @@ namespace // Manhattan: // - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z| // - faster but not the shortest path - float costAStar(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + float costAStar(const ESM::Pathgrid::Point a, const ESM::Pathgrid::Point b) { //return distance(a, b); return manhattan(a, b); @@ -113,12 +113,13 @@ namespace return closestIndex; } - // Uses mSCComp to choose a reachable end pathgrid point. start is assumed reachable. + // Chooses a reachable end pathgrid point. start is assumed reachable. std::pair getClosestReachablePoint(const ESM::Pathgrid* grid, - Ogre::Vector3 pos, int start, std::vector &sCComp) + const MWWorld::CellStore *cell, + Ogre::Vector3 pos, int start) { - // assume grid is fine - int startGroup = sCComp[start]; + if(!grid || grid->mPoints.empty()) + return std::pair (-1, false); float distanceBetween = distanceSquared(grid->mPoints[0], pos); int closestIndex = 0; @@ -133,7 +134,7 @@ namespace // found a closer one distanceBetween = potentialDistBetween; closestIndex = counter; - if (sCComp[counter] == startGroup) + if (cell->isPointConnected(start, counter)) { closestReachableIndex = counter; } @@ -152,7 +153,7 @@ namespace MWMechanics { PathFinder::PathFinder() : mIsPathConstructed(false), - mIsGraphConstructed(false), + mPathgrid(NULL), mCell(NULL) { } @@ -164,293 +165,14 @@ namespace MWMechanics mIsPathConstructed = false; } - /* - * NOTE: Based on buildPath2(), please check git history if interested - * - * Populate mGraph with the cost of each allowed edge. - * - * Any existing data in mGraph is wiped clean first. The node's parent - * is set with initial value of -1. The parent values are populated by - * aStarSearch() in order to reconstruct a path. - * - * mGraph[f].edges[n].destination = t - * - * f = point index of location "from" - * t = point index of location "to" - * n = index of edges from point f - * - * - * Example: (note from p(0) to p(2) not allowed in this example) - * - * mGraph[0].edges[0].destination = 1 - * .edges[1].destination = 3 - * - * mGraph[1].edges[0].destination = 0 - * .edges[1].destination = 2 - * .edges[2].destination = 3 - * - * mGraph[2].edges[0].destination = 1 - * - * (etc, etc) - * - * - * low - * cost - * p(0) <---> p(1) <------------> p(2) - * ^ ^ - * | | - * | +-----> p(3) - * +----------------> - * high cost - */ - void PathFinder::buildPathgridGraph(const ESM::Pathgrid* pathGrid) - { - mGraph.clear(); - // resize lists - mGScore.resize(pathGrid->mPoints.size(), -1); - mFScore.resize(pathGrid->mPoints.size(), -1); - Node defaultNode; - defaultNode.label = -1; - defaultNode.parent = -1; - mGraph.resize(pathGrid->mPoints.size(),defaultNode); - // initialise mGraph - for(unsigned int i = 0; i < pathGrid->mPoints.size(); i++) - { - Node node; - node.label = i; - node.parent = -1; - mGraph[i] = node; - } - // store the costs of each edge - for(unsigned int i = 0; i < pathGrid->mEdges.size(); i++) - { - Edge edge; - edge.cost = costAStar(pathGrid->mPoints[pathGrid->mEdges[i].mV0], - pathGrid->mPoints[pathGrid->mEdges[i].mV1]); - // forward path of the edge - edge.destination = pathGrid->mEdges[i].mV1; - mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge); - // reverse path of the edge - // NOTE: These are redundant, the ESM already contains the reverse paths. - //edge.destination = pathGrid->mEdges[i].mV0; - //mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); - } - mIsGraphConstructed = true; - } - - // v is the pathgrid point index (some call them vertices) - void PathFinder::recursiveStrongConnect(int v) - { - mSCCPoint[v].first = mSCCIndex; // index - mSCCPoint[v].second = mSCCIndex; // lowlink - mSCCIndex++; - mSCCStack.push_back(v); - int w; - - for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) - { - w = mGraph[v].edges[i].destination; - if(mSCCPoint[w].first == -1) // not visited - { - recursiveStrongConnect(w); // recurse - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].second); - } - else - { - if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].first); - } - } - - if(mSCCPoint[v].second == mSCCPoint[v].first) - { - // new component - do - { - w = mSCCStack.back(); - mSCCStack.pop_back(); - mSCComp[w] = mSCCId; - } - while(w != v); - - mSCCId++; - } - return; - } - - /* - * mSCComp contains the strongly connected component group id's. - * - * A cell can have disjointed pathgrid, e.g. Seyda Neen which has 3 - * - * mSCComp for Seyda Neen will have 3 different values. When selecting a - * random pathgrid point for AiWander, mSCComp can be checked for quickly - * finding whether the destination is reachable. - * - * Otherwise, buildPath will automatically select a closest reachable end - * pathgrid point (reachable from the closest start point). - * - * Using Tarjan's algorithm - * - * mGraph | graph G | - * mSCCPoint | V | derived from pathGrid->mPoints - * mGraph[v].edges | E (for v) | - * mSCCIndex | index | keep track of smallest unused index - * mSCCStack | S | - * pathGrid - * ->mEdges[v].mV1 | w | = mGraph[v].edges[i].destination - * - * FIXME: Some of these can be cleaned up by including them to struct - * Node used by mGraph - */ - void PathFinder::buildConnectedPoints(const ESM::Pathgrid* pathGrid) - { - mSCComp.clear(); - mSCComp.resize(pathGrid->mPoints.size(), 0); - mSCCId = 0; - - mSCCIndex = 0; - mSCCStack.clear(); - mSCCPoint.clear(); - mSCCPoint.resize(pathGrid->mPoints.size(), std::pair (-1, -1)); - - for(unsigned int v = 0; v < pathGrid->mPoints.size(); v++) - { - if(mSCCPoint[v].first == -1) // undefined (haven't visited) - recursiveStrongConnect(v); - } - } - - void PathFinder::cleanUpAStar() - { - for(int i = 0; i < static_cast (mGraph.size()); i++) - { - mGraph[i].parent = -1; - mGScore[i] = -1; - mFScore[i] = -1; - } - } - - /* - * NOTE: Based on buildPath2(), please check git history if interested - * Should consider a using 3rd party library version (e.g. boost) - * - * Find the shortest path to the target goal using a well known algorithm. - * Uses mGraph which has pre-computed costs for allowed edges. It is assumed - * that mGraph is already constructed. The caller, i.e. buildPath(), needs - * to ensure this. - * - * Returns path (a list of pathgrid point indexes) which may be empty. - * - * Input params: - * start, goal - pathgrid point indexes (for this cell) - * xCell, yCell - values to add to convert path back to world scale - * - * Variables: - * openset - point indexes to be traversed, lowest cost at the front - * closedset - point indexes already traversed - * - * Class variables: - * mGScore - past accumulated costs vector indexed by point index - * mFScore - future estimated costs vector indexed by point index - * these are resized by buildPathgridGraph() - */ - std::list PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid, - int start, int goal, - float xCell, float yCell) - { - cleanUpAStar(); - // mGScore & mFScore keep costs for each pathgrid point in pathGrid->mPoints - mGScore[start] = 0; - mFScore[start] = costAStar(pathGrid->mPoints[start], pathGrid->mPoints[goal]); - - std::list openset; - std::list closedset; - openset.push_back(start); - - int current = -1; - - while(!openset.empty()) - { - current = openset.front(); // front has the lowest cost - openset.pop_front(); - - if(current == goal) - break; - - closedset.push_back(current); // remember we've been here - - // check all edges for the current point index - for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) - { - if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].destination) == - closedset.end()) - { - // not in closedset - i.e. have not traversed this edge destination - int dest = mGraph[current].edges[j].destination; - float tentative_g = mGScore[current] + mGraph[current].edges[j].cost; - bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); - if(!isInOpenSet - || tentative_g < mGScore[dest]) - { - mGraph[dest].parent = current; - mGScore[dest] = tentative_g; - mFScore[dest] = tentative_g + - costAStar(pathGrid->mPoints[dest], pathGrid->mPoints[goal]); - if(!isInOpenSet) - { - // add this edge to openset, lowest cost goes to the front - // TODO: if this causes performance problems a hash table may help - std::list::iterator it = openset.begin(); - for(it = openset.begin(); it!= openset.end(); it++) - { - if(mFScore[*it] > mFScore[dest]) - break; - } - openset.insert(it, dest); - } - } - } // if in closedset, i.e. traversed this edge already, try the next edge - } - } - - std::list path; - if(current != goal) - return path; // for some reason couldn't build a path - // e.g. start was not reachable (we assume it is) - - // reconstruct path to return, using world co-ordinates - while(mGraph[current].parent != -1) - { - ESM::Pathgrid::Point pt = pathGrid->mPoints[current]; - pt.mX += xCell; - pt.mY += yCell; - path.push_front(pt); - current = mGraph[current].parent; - } - - // TODO: Is this a bug? If path is empty the algorithm couldn't find a path. - // Simply using the destination as the path in this scenario seems strange. - // Commented out pending further testing. -#if 0 - if(path.empty()) - { - ESM::Pathgrid::Point pt = pathGrid->mPoints[goal]; - pt.mX += xCell; - pt.mY += yCell; - path.push_front(pt); - } -#endif - return path; - } - /* * NOTE: This method may fail to find a path. The caller must check the * result before using it. If there is no path the AI routies need to * implement some other heuristics to reach the target. * + * NOTE: It may be desirable to simply go directly to the endPoint if for + * example there are no pathgrids in this cell. + * * NOTE: startPoint & endPoint are in world co-ordinates * * Updates mPath using aStarSearch() or ray test (if shortcut allowed). @@ -462,9 +184,6 @@ namespace MWMechanics * * mPathConstructed is set true if successful, false if not * - * May update mGraph by calling buildPathgridGraph() if it isn't - * constructed yet. At the same time mConnectedPoints is also updated. - * * NOTE: co-ordinates must be converted prior to calling getClosestPoint() * * | @@ -486,7 +205,8 @@ namespace MWMechanics */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, bool allowShortcuts) + const MWWorld::CellStore* cell, + bool allowShortcuts) { mPath.clear(); @@ -502,48 +222,76 @@ namespace MWMechanics } } - if(mCell != cell) + if(mCell != cell || !mPathgrid) { - mIsGraphConstructed = false; // must be in a new cell, need a new mGraph and mSCComp mCell = cell; + + // Cache pathgrid as mPathgrid and update on cell changes. There + // might be a small gain in avoiding to search for it. + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); + } + + // Refer to AiWander reseach topic on openmw forums for some background. + // Maybe there is no pathgrid for this cell. Just go to destination and let + // physics take care of any blockages. + if(!mPathgrid || mPathgrid->mPoints.empty()) + { +//#if 0 + std::cout << "no pathgrid " << + +"\"" +mCell->getCell()->mName+ "\"" + +", " +std::to_string(mCell->getCell()->mData.mX) + +", " +std::to_string(mCell->getCell()->mData.mY) + << std::endl; +//#endif + mPath.push_back(endPoint); + mIsPathConstructed = true; + return; } - const ESM::Pathgrid *pathGrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); + // NOTE: getClosestPoint expects local co-ordinates float xCell = 0; float yCell = 0; - if (mCell->isExterior()) { xCell = mCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; yCell = mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; } + + + + + + + + + + // NOTE: It is possible that getClosestPoint returns a pathgrind point index // that is unreachable in some situations. e.g. actor is standing // outside an area enclosed by walls, but there is a pathgrid // point right behind the wall that is closer than any pathgrid // point outside the wall - // - // NOTE: getClosestPoint expects local co-ordinates - // - int startNode = getClosestPoint(pathGrid, - Ogre::Vector3(startPoint.mX - xCell, startPoint.mY - yCell, startPoint.mZ)); - - if(startNode != -1) // only check once, assume pathGrid won't change + int startNode = getClosestPoint(mPathgrid, + Ogre::Vector3(startPoint.mX - xCell, startPoint.mY - yCell, startPoint.mZ)); + // Some cells don't have any pathgrids at all + if(startNode != -1) { - if(!mIsGraphConstructed) - { - buildPathgridGraph(pathGrid); // pre-compute costs for use with aStarSearch - buildConnectedPoints(pathGrid); // must before calling getClosestReachablePoint - } - std::pair endNode = getClosestReachablePoint(pathGrid, + std::pair endNode = getClosestReachablePoint(mPathgrid, cell, Ogre::Vector3(endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ), - startNode, mSCComp); - + startNode); +//#if 0 + if(!mPathgrid) + std::cout << "no pathgrid " << + +"\"" +mCell->getCell()->mName+ "\"" + +", " +std::to_string(mCell->getCell()->mData.mX) + +", " +std::to_string(mCell->getCell()->mData.mY) + << std::endl; +//#endif + // this shouldn't really happen, but just in case if(endNode.first != -1) { - mPath = aStarSearch(pathGrid, startNode, endNode.first, xCell, yCell); + mPath = mCell->aStarSearch(startNode, endNode.first, mCell->isExterior()); if(!mPath.empty()) { @@ -561,13 +309,34 @@ namespace MWMechanics mPath.push_back(endPoint); } else + { mIsPathConstructed = false; + std::cout << "empty path error " << std::endl; + } + //mIsPathConstructed = false; } else + { mIsPathConstructed = false; + std::cout << "second point error " << std::endl; + } + //mIsPathConstructed = false; } else - mIsPathConstructed = false; // this shouldn't really happen, but just in case + { + // FIXME: shouldn't return endpoint if first point error? + mIsPathConstructed = false; + std::cout << "first point error " << std::endl; + } + +#if 0 + if(!mIsPathConstructed) + { + mPath.push_back(endPoint); + mIsPathConstructed = true; + } +#endif + return; } float PathFinder::getZAngleToNext(float x, float y) const @@ -645,5 +414,326 @@ namespace MWMechanics } + // TODO: Any multi threading concerns? + PathgridGraph::PathgridGraph() + : mCell(NULL) + , mIsGraphConstructed(false) + , mPathgrid(NULL) + , mGraph(0) + , mSCCId(0) + , mSCCIndex(0) + { + } + + /* + * mGraph is populated with the cost of each allowed edge. + * + * The data structure is based on the code in buildPath2() but modified. + * Please check git history if interested. + * + * mGraph[v].edges[i].index = w + * + * v = point index of location "from" + * i = index of edges from point v + * w = point index of location "to" + * + * + * Example: (notice from p(0) to p(2) is not allowed in this example) + * + * mGraph[0].edges[0].index = 1 + * .edges[1].index = 3 + * + * mGraph[1].edges[0].index = 0 + * .edges[1].index = 2 + * .edges[2].index = 3 + * + * mGraph[2].edges[0].index = 1 + * + * (etc, etc) + * + * + * low + * cost + * p(0) <---> p(1) <------------> p(2) + * ^ ^ + * | | + * | +-----> p(3) + * +----------------> + * high cost + */ + bool PathgridGraph::initPathgridGraph(const ESM::Cell* cell) + { + if(!cell) + { + std::cout << "init error " << std::endl; + return false; + } + mCell = cell; + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); + + if(!mPathgrid) + { + std::cout << "init error " << std::endl; + return false; + } + + mGraph.resize(mPathgrid->mPoints.size()); + for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) + { + ConnectedPoint neighbour; + neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0], + mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]); + // forward path of the edge + neighbour.index = mPathgrid->mEdges[i].mV1; + mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour); + // reverse path of the edge + // NOTE: These are redundant, ESM already contains the required reverse paths + //neighbour.index = mPathgrid->mEdges[i].mV0; + //mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour); + } + buildConnectedPoints(); + mIsGraphConstructed = true; + //#if 0 + std::cout << "loading pathgrid " << + +"\""+ mPathgrid->mCell +"\"" + +", "+ std::to_string(mPathgrid->mData.mX) + +", "+ std::to_string(mPathgrid->mData.mY) + << std::endl; + //#endif + return true; + } + + // v is the pathgrid point index (some call them vertices) + void PathgridGraph::recursiveStrongConnect(int v) + { + mSCCPoint[v].first = mSCCIndex; // index + mSCCPoint[v].second = mSCCIndex; // lowlink + mSCCIndex++; + mSCCStack.push_back(v); + int w; + + for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) + { + w = mGraph[v].edges[i].index; + if(mSCCPoint[w].first == -1) // not visited + { + recursiveStrongConnect(w); // recurse + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].second); + } + else + { + if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].first); + } + } + + if(mSCCPoint[v].second == mSCCPoint[v].first) + { // new component + do + { + w = mSCCStack.back(); + mSCCStack.pop_back(); + mGraph[w].componentId = mSCCId; + } + while(w != v); + mSCCId++; + } + return; + } + + /* + * mGraph contains the strongly connected component group id's along + * with pre-calculated edge costs. + * + * A cell can have disjointed pathgrids, e.g. Seyda Neen has 3 + * + * mGraph for Seyda Neen will therefore have 3 different values. When + * selecting a random pathgrid point for AiWander, mGraph can be checked + * for quickly finding whether the destination is reachable. + * + * Otherwise, buildPath can automatically select a closest reachable end + * pathgrid point (reachable from the closest start point). + * + * Using Tarjan's algorithm: + * + * mGraph | graph G | + * mSCCPoint | V | derived from mPoints + * mGraph[v].edges | E (for v) | + * mSCCIndex | index | tracking smallest unused index + * mSCCStack | S | + * mGraph[v].edges[i].index | w | + * + */ + void PathgridGraph::buildConnectedPoints() + { + // both of these are set to zero in the constructor + //mSCCId = 0; // how many strongly connected components in this cell + //mSCCIndex = 0; + int pointsSize = mPathgrid->mPoints.size(); + mSCCPoint.resize(pointsSize, std::pair (-1, -1)); + mSCCStack.reserve(pointsSize); + + for(int v = 0; v < static_cast (pointsSize); v++) + { + if(mSCCPoint[v].first == -1) // undefined (haven't visited) + recursiveStrongConnect(v); + } + //#if 0 + std::cout << "components: " << std::to_string(mSCCId) + +", "+ mPathgrid->mCell + << std::endl; + //#endif + } + + bool PathgridGraph::isPointConnected(const int start, const int end) const + { + return (mGraph[start].componentId == mGraph[end].componentId); + } + + /* + * NOTE: Based on buildPath2(), please check git history if interested + * Should consider using a 3rd party library version (e.g. boost) + * + * Find the shortest path to the target goal using a well known algorithm. + * Uses mGraph which has pre-computed costs for allowed edges. It is assumed + * that mGraph is already constructed. + * + * Should be possible to make this MT safe. + * + * Returns path (a list of pathgrid point indexes) which may be empty. + * + * Input params: + * start, goal - pathgrid point indexes (for this cell) + * isExterior - used to determine whether to convert to world co-ordinates + * + * Variables: + * openset - point indexes to be traversed, lowest cost at the front + * closedset - point indexes already traversed + * gScore - past accumulated costs vector indexed by point index + * fScore - future estimated costs vector indexed by point index + * + * TODO: An intersting exercise might be to cache the paths created for a + * start/goal pair. To cache the results the paths need to be in + * pathgrid points form (currently they are converted to world + * co-ordinates). Essentially trading speed w/ memory. + */ + std::list PathgridGraph::aStarSearch(const int start, + const int goal, + bool isExterior) const + { + std::list path; + if(!isPointConnected(start, goal)) + { + return path; // there is no path, return an empty path + } + + int graphSize = mGraph.size(); + std::vector gScore; + gScore.resize(graphSize, -1); + std::vector fScore; + fScore.resize(graphSize, -1); + std::vector graphParent; + graphParent.resize(graphSize, -1); + + // gScore & fScore keep costs for each pathgrid point in mPoints + gScore[start] = 0; + fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]); + + std::list openset; + std::list closedset; + openset.push_back(start); + + int current = -1; + + while(!openset.empty()) + { + current = openset.front(); // front has the lowest cost + openset.pop_front(); + + if(current == goal) + break; + + closedset.push_back(current); // remember we've been here + + // check all edges for the current point index + for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) + { + if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) == + closedset.end()) + { + // not in closedset - i.e. have not traversed this edge destination + int dest = mGraph[current].edges[j].index; + float tentative_g = gScore[current] + mGraph[current].edges[j].cost; + bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); + if(!isInOpenSet + || tentative_g < gScore[dest]) + { + graphParent[dest] = current; + gScore[dest] = tentative_g; + fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest], + mPathgrid->mPoints[goal]); + if(!isInOpenSet) + { + // add this edge to openset, lowest cost goes to the front + // TODO: if this causes performance problems a hash table may help + std::list::iterator it = openset.begin(); + for(it = openset.begin(); it!= openset.end(); it++) + { + if(fScore[*it] > fScore[dest]) + break; + } + openset.insert(it, dest); + } + } + } // if in closedset, i.e. traversed this edge already, try the next edge + } + } + + if(current != goal) + return path; // for some reason couldn't build a path + + // reconstruct path to return, using world co-ordinates + float xCell = 0; + float yCell = 0; + if (isExterior) + { + xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; + yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; + } +//#if 0 + // for debugging only + int tmp = current; + if(tmp != goal) + { + std::cout << "aStarSearch: goal and result differ" << std::endl; + std::cout << "goal: " << std::to_string(goal) + +", result: "+ std::to_string(tmp) + << std::endl; + } + std::cout << "start: " << std::to_string(start) + +", goal: "+ std::to_string(goal) + +", result: "+ std::to_string(tmp) + << std::endl; +//#endif + + while(graphParent[current] != -1) + { + ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; +//#if 0 + // for debugging only + std::cout << " point: "+ std::to_string(current) + +", X: "+ std::to_string(pt.mX) + +", Y: "+ std::to_string(pt.mY) + << std::endl; +//#endif + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); + current = graphParent[current]; + } + return path; + } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index ae849bff2..fddb293d9 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -2,6 +2,7 @@ #define GAME_MWMECHANICS_PATHFINDING_H #include +#include #include #include @@ -34,8 +35,6 @@ namespace MWMechanics void clearPath(); - void buildPathgridGraph(const ESM::Pathgrid* pathGrid); - void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, const MWWorld::CellStore* cell, bool allowShortcuts = true); @@ -75,60 +74,73 @@ namespace MWMechanics mPath.push_back(point); } - // While a public method is defined here, it is anticipated that - // mSCComp will only be used internally. - std::vector getSCComp() const - { - return mSCComp; - } - private: - struct Edge - { - int destination; - float cost; - }; - struct Node + bool mIsPathConstructed; + + std::list mPath; + + const ESM::Pathgrid *mPathgrid; + const MWWorld::CellStore* mCell; + }; + + class PathgridGraph + { + public: + PathgridGraph(); + + bool isGraphConstructed() const { - int label; - std::vector edges; - int parent;//used in pathfinding + return mIsGraphConstructed; }; - std::vector mGScore; - std::vector mFScore; + bool initPathgridGraph(const ESM::Cell *cell); - std::list aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell = 0, float yCell = 0); - void cleanUpAStar(); + // returns true if end point is strongly connected (i.e. reachable + // from start point) both start and end are pathgrid point indexes + bool isPointConnected(const int start, const int end) const; - std::vector mGraph; - bool mIsPathConstructed; + // isOutside is used whether to convert path to world co-ordinates + std::list aStarSearch(const int start, const int end, + const bool isOutside) const; + private: + const ESM::Cell *mCell; + const ESM::Pathgrid *mPathgrid; - std::list mPath; - bool mIsGraphConstructed; - const MWWorld::CellStore* mCell; + struct ConnectedPoint // edge + { + int index; // pathgrid point index of neighbour + float cost; + }; + + struct Node // point + { + int componentId; + std::vector edges; // neighbours + }; - // contains an integer indicating the groups of connected pathgrid points - // (all connected points will have the same value) + // componentId is an integer indicating the groups of connected + // pathgrid points (all connected points will have the same value) // // In Seyda Neen there are 3: // // 52, 53 and 54 are one set (enclosed yard) - // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 are another (ship & office) + // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office) // all other pathgrid points are the third set // - std::vector mSCComp; - // variables used to calculate mSCComp + std::vector mGraph; + bool mIsGraphConstructed; + + // variables used to calculate connected components int mSCCId; int mSCCIndex; - std::list mSCCStack; + std::vector mSCCStack; typedef std::pair VPair; // first is index, second is lowlink std::vector mSCCPoint; - // methods used to calculate mSCComp + // methods used to calculate connected components void recursiveStrongConnect(int v); - void buildConnectedPoints(const ESM::Pathgrid* pathGrid); + void buildConnectedPoints(); }; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 3f1ef8ab2..dd105a665 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -680,4 +680,34 @@ namespace MWWorld { return !(left==right); } + + bool CellStore::isPointConnected(const int start, const int end) const + { + if(!mPathgridGraph.isGraphConstructed()) + { + // Ugh... there must be a better way... + MWMechanics::PathgridGraph *p = const_cast (&mPathgridGraph); + + if(!p->initPathgridGraph(mCell)) + return false; + } + return mPathgridGraph.isPointConnected(start, end); + + } + + std::list CellStore::aStarSearch(const int start, const int end, + const bool isOutside) const + { + if(!mPathgridGraph.isGraphConstructed()) + { + MWMechanics::PathgridGraph *p = const_cast (&mPathgridGraph); + + if(!p->initPathgridGraph(mCell)) + { + std::list path; // empty + return path; + } + } + return mPathgridGraph.aStarSearch(start, end, isOutside); + } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 4b7c0011b..3cca993f2 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -8,6 +8,8 @@ #include "esmstore.hpp" #include "cellreflist.hpp" +#include "../mwmechanics/pathfinding.hpp" + namespace ESM { struct CellState; @@ -141,6 +143,11 @@ namespace MWWorld throw std::runtime_error ("Storage for this type not exist in cells"); } + bool isPointConnected(const int start, const int end) const; + + std::list aStarSearch(const int start, const int end, + const bool isOutside) const; + private: template @@ -166,6 +173,8 @@ namespace MWWorld ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. + + MWMechanics::PathgridGraph mPathgridGraph; }; template<> From 5d422fec8abd5f52adb1de423cd0effb3d4a6cb4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 3 Apr 2014 13:00:19 +0200 Subject: [PATCH 42/85] fixed scene toolbar layout problems --- apps/opencs/view/world/scenetool.cpp | 1 + apps/opencs/view/world/scenetoolbar.cpp | 7 ++++++- apps/opencs/view/world/scenetoolbar.hpp | 3 +++ apps/opencs/view/world/scenetoolmode.cpp | 3 ++- apps/opencs/view/world/scenetoolmode.hpp | 1 + 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/scenetool.cpp b/apps/opencs/view/world/scenetool.cpp index 320deb1ba..612b4c6d3 100644 --- a/apps/opencs/view/world/scenetool.cpp +++ b/apps/opencs/view/world/scenetool.cpp @@ -6,6 +6,7 @@ CSVWorld::SceneTool::SceneTool (SceneToolbar *parent) : QPushButton (parent) { setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); + setIconSize (QSize (parent->getIconSize(), parent->getIconSize())); setFixedSize (parent->getButtonSize(), parent->getButtonSize()); connect (this, SIGNAL (clicked()), this, SLOT (openRequest())); diff --git a/apps/opencs/view/world/scenetoolbar.cpp b/apps/opencs/view/world/scenetoolbar.cpp index 2972c5391..9eb02ce2f 100644 --- a/apps/opencs/view/world/scenetoolbar.cpp +++ b/apps/opencs/view/world/scenetoolbar.cpp @@ -6,7 +6,7 @@ #include "scenetool.hpp" CSVWorld::SceneToolbar::SceneToolbar (int buttonSize, QWidget *parent) -: QWidget (parent), mButtonSize (buttonSize) +: QWidget (parent), mButtonSize (buttonSize), mIconSize (buttonSize-8) { setFixedWidth (mButtonSize); @@ -26,4 +26,9 @@ void CSVWorld::SceneToolbar::addTool (SceneTool *tool) int CSVWorld::SceneToolbar::getButtonSize() const { return mButtonSize; +} + +int CSVWorld::SceneToolbar::getIconSize() const +{ + return mIconSize; } \ No newline at end of file diff --git a/apps/opencs/view/world/scenetoolbar.hpp b/apps/opencs/view/world/scenetoolbar.hpp index f713ca3df..731806cc5 100644 --- a/apps/opencs/view/world/scenetoolbar.hpp +++ b/apps/opencs/view/world/scenetoolbar.hpp @@ -15,6 +15,7 @@ namespace CSVWorld QVBoxLayout *mLayout; int mButtonSize; + int mIconSize; public: @@ -23,6 +24,8 @@ namespace CSVWorld void addTool (SceneTool *tool); int getButtonSize() const; + + int getIconSize() const; }; } diff --git a/apps/opencs/view/world/scenetoolmode.cpp b/apps/opencs/view/world/scenetoolmode.cpp index 281d703b6..73b01ae3a 100644 --- a/apps/opencs/view/world/scenetoolmode.cpp +++ b/apps/opencs/view/world/scenetoolmode.cpp @@ -8,7 +8,7 @@ #include "scenetoolbar.hpp" CSVWorld::SceneToolMode::SceneToolMode (SceneToolbar *parent) -: SceneTool (parent), mButtonSize (parent->getButtonSize()) +: SceneTool (parent), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()) { mPanel = new QFrame (this, Qt::Popup); @@ -29,6 +29,7 @@ void CSVWorld::SceneToolMode::addButton (const std::string& icon, const std::str { QPushButton *button = new QPushButton (QIcon (QPixmap (icon.c_str())), "", mPanel); button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); + button->setIconSize (QSize (mIconSize, mIconSize)); button->setFixedSize (mButtonSize, mButtonSize); mLayout->addWidget (button); diff --git a/apps/opencs/view/world/scenetoolmode.hpp b/apps/opencs/view/world/scenetoolmode.hpp index a8fe2b5a6..a156c0c95 100644 --- a/apps/opencs/view/world/scenetoolmode.hpp +++ b/apps/opencs/view/world/scenetoolmode.hpp @@ -20,6 +20,7 @@ namespace CSVWorld QHBoxLayout *mLayout; std::map mButtons; // widget, id int mButtonSize; + int mIconSize; public: From 325d0616bbe8e10d661fff03b9e64778865698b1 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 3 Apr 2014 22:17:45 +1100 Subject: [PATCH 43/85] Cleanup debug statements. --- apps/openmw/mwmechanics/pathfinding.cpp | 85 ++----------------------- 1 file changed, 6 insertions(+), 79 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index f56cfad5f..1ef05eda8 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -225,9 +225,6 @@ namespace MWMechanics if(mCell != cell || !mPathgrid) { mCell = cell; - - // Cache pathgrid as mPathgrid and update on cell changes. There - // might be a small gain in avoiding to search for it. mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); } @@ -236,13 +233,6 @@ namespace MWMechanics // physics take care of any blockages. if(!mPathgrid || mPathgrid->mPoints.empty()) { -//#if 0 - std::cout << "no pathgrid " << - +"\"" +mCell->getCell()->mName+ "\"" - +", " +std::to_string(mCell->getCell()->mData.mX) - +", " +std::to_string(mCell->getCell()->mData.mY) - << std::endl; -//#endif mPath.push_back(endPoint); mIsPathConstructed = true; return; @@ -257,16 +247,6 @@ namespace MWMechanics yCell = mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; } - - - - - - - - - - // NOTE: It is possible that getClosestPoint returns a pathgrind point index // that is unreachable in some situations. e.g. actor is standing // outside an area enclosed by walls, but there is a pathgrid @@ -280,14 +260,7 @@ namespace MWMechanics std::pair endNode = getClosestReachablePoint(mPathgrid, cell, Ogre::Vector3(endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ), startNode); -//#if 0 - if(!mPathgrid) - std::cout << "no pathgrid " << - +"\"" +mCell->getCell()->mName+ "\"" - +", " +std::to_string(mCell->getCell()->mData.mX) - +", " +std::to_string(mCell->getCell()->mData.mY) - << std::endl; -//#endif + // this shouldn't really happen, but just in case if(endNode.first != -1) { @@ -309,33 +282,14 @@ namespace MWMechanics mPath.push_back(endPoint); } else - { mIsPathConstructed = false; - std::cout << "empty path error " << std::endl; - } - //mIsPathConstructed = false; } else - { mIsPathConstructed = false; - std::cout << "second point error " << std::endl; - } - //mIsPathConstructed = false; } else - { - // FIXME: shouldn't return endpoint if first point error? mIsPathConstructed = false; - std::cout << "first point error " << std::endl; - } -#if 0 - if(!mIsPathConstructed) - { - mPath.push_back(endPoint); - mIsPathConstructed = true; - } -#endif return; } @@ -464,18 +418,13 @@ namespace MWMechanics bool PathgridGraph::initPathgridGraph(const ESM::Cell* cell) { if(!cell) - { - std::cout << "init error " << std::endl; return false; - } + mCell = cell; mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); if(!mPathgrid) - { - std::cout << "init error " << std::endl; return false; - } mGraph.resize(mPathgrid->mPoints.size()); for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) @@ -493,13 +442,13 @@ namespace MWMechanics } buildConnectedPoints(); mIsGraphConstructed = true; - //#if 0 +//#if 0 std::cout << "loading pathgrid " << +"\""+ mPathgrid->mCell +"\"" +", "+ std::to_string(mPathgrid->mData.mX) +", "+ std::to_string(mPathgrid->mData.mY) << std::endl; - //#endif +//#endif return true; } @@ -580,11 +529,11 @@ namespace MWMechanics if(mSCCPoint[v].first == -1) // undefined (haven't visited) recursiveStrongConnect(v); } - //#if 0 +//#if 0 std::cout << "components: " << std::to_string(mSCCId) +", "+ mPathgrid->mCell << std::endl; - //#endif +//#endif } bool PathgridGraph::isPointConnected(const int start, const int end) const @@ -702,32 +651,10 @@ namespace MWMechanics xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; } -//#if 0 - // for debugging only - int tmp = current; - if(tmp != goal) - { - std::cout << "aStarSearch: goal and result differ" << std::endl; - std::cout << "goal: " << std::to_string(goal) - +", result: "+ std::to_string(tmp) - << std::endl; - } - std::cout << "start: " << std::to_string(start) - +", goal: "+ std::to_string(goal) - +", result: "+ std::to_string(tmp) - << std::endl; -//#endif while(graphParent[current] != -1) { ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; -//#if 0 - // for debugging only - std::cout << " point: "+ std::to_string(current) - +", X: "+ std::to_string(pt.mX) - +", Y: "+ std::to_string(pt.mY) - << std::endl; -//#endif pt.mX += xCell; pt.mY += yCell; path.push_front(pt); From 3a58da9ad78ade99a388923dafdfb29bc4110d8d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 3 Apr 2014 13:30:22 +0200 Subject: [PATCH 44/85] size adjustment --- apps/opencs/view/world/scenetoolbar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/world/scenetoolbar.cpp b/apps/opencs/view/world/scenetoolbar.cpp index 9eb02ce2f..d60240da7 100644 --- a/apps/opencs/view/world/scenetoolbar.cpp +++ b/apps/opencs/view/world/scenetoolbar.cpp @@ -6,7 +6,7 @@ #include "scenetool.hpp" CSVWorld::SceneToolbar::SceneToolbar (int buttonSize, QWidget *parent) -: QWidget (parent), mButtonSize (buttonSize), mIconSize (buttonSize-8) +: QWidget (parent), mButtonSize (buttonSize), mIconSize (buttonSize-6) { setFixedWidth (mButtonSize); From a8b2eb1fe9f16e51602c6cee8717082211555fbc Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 3 Apr 2014 22:49:22 +1100 Subject: [PATCH 45/85] Make Travis happy. --- apps/openmw/mwmechanics/pathfinding.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 1ef05eda8..9fcd335e4 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -442,13 +442,13 @@ namespace MWMechanics } buildConnectedPoints(); mIsGraphConstructed = true; -//#if 0 +#if 0 std::cout << "loading pathgrid " << +"\""+ mPathgrid->mCell +"\"" +", "+ std::to_string(mPathgrid->mData.mX) +", "+ std::to_string(mPathgrid->mData.mY) << std::endl; -//#endif +#endif return true; } @@ -529,11 +529,11 @@ namespace MWMechanics if(mSCCPoint[v].first == -1) // undefined (haven't visited) recursiveStrongConnect(v); } -//#if 0 +#if 0 std::cout << "components: " << std::to_string(mSCCId) +", "+ mPathgrid->mCell << std::endl; -//#endif +#endif } bool PathgridGraph::isPointConnected(const int start, const int end) const From baf30ba29295a8535f3af18961060e72d8ec9a09 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 3 Apr 2014 14:44:48 +0200 Subject: [PATCH 46/85] added grid tool (does not work yet) --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/scenesubview.cpp | 13 ++++ apps/opencs/view/world/scenetoolgrid.cpp | 75 ++++++++++++++++++++++++ apps/opencs/view/world/scenetoolgrid.hpp | 29 +++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/view/world/scenetoolgrid.cpp create mode 100644 apps/opencs/view/world/scenetoolgrid.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index a7a694463..05cc93f89 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool - scenetoolmode infocreator scriptedit dialoguesubview previewsubview + scenetoolmode infocreator scriptedit dialoguesubview previewsubview scenetoolgrid ) opencs_units (view/render diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index c075cb4d6..dedaf014b 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -18,6 +18,7 @@ #include "creator.hpp" #include "scenetoolbar.hpp" #include "scenetoolmode.hpp" +#include "scenetoolgrid.hpp" CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id) @@ -36,6 +37,8 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolbar *toolbar = new SceneToolbar (48, this); + SceneToolGrid *gridTool = 0; + if (id.getId()=="sys::default") { CSVRender::PagedWorldspaceWidget *widget = new CSVRender::PagedWorldspaceWidget (this); @@ -44,6 +47,13 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), this, SLOT (cellIndexChanged (const std::pair&, const std::pair&))); + + gridTool = new SceneToolGrid (toolbar); + + connect (widget, + SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), + gridTool, + SLOT (cellIndexChanged (const std::pair&, const std::pair&))); } else mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); @@ -54,6 +64,9 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); toolbar->addTool (lightingTool); + if (gridTool) + toolbar->addTool (gridTool); + layout2->addWidget (toolbar, 0); layout2->addWidget (mScene, 1); diff --git a/apps/opencs/view/world/scenetoolgrid.cpp b/apps/opencs/view/world/scenetoolgrid.cpp new file mode 100644 index 000000000..0769be168 --- /dev/null +++ b/apps/opencs/view/world/scenetoolgrid.cpp @@ -0,0 +1,75 @@ + +#include "scenetoolgrid.hpp" + +#include + +#include +#include +#include + +#include "scenetoolbar.hpp" + +CSVWorld::SceneToolGrid::SceneToolGrid (SceneToolbar *parent) +: SceneTool (parent), mIconSize (parent->getIconSize()) +{ +} + +void CSVWorld::SceneToolGrid::showPanel (const QPoint& position) +{ + + +} + +void CSVWorld::SceneToolGrid::cellIndexChanged (const std::pair& min, + const std::pair& max) +{ + /// \todo make font size configurable + const int fontSize = 8; + + /// \todo replace with proper icon + QPixmap image (mIconSize, mIconSize); + image.fill (QColor (0, 0, 0, 0)); + + { + QPainter painter (&image); + painter.setPen (Qt::black); + QFont font (QApplication::font().family(), fontSize); + painter.setFont (font); + + QFontMetrics metrics (font); + + if (min==max) + { + // single cell + std::ostringstream stream; + stream << min.first << ", " << min.second; + + QString text = QString::fromUtf8 (stream.str().c_str()); + + painter.drawText (QPoint ((mIconSize-metrics.width (text))/2, mIconSize/2+fontSize/2), + text); + } + else + { + // range + { + std::ostringstream stream; + stream << min.first << ", " << min.second; + painter.drawText (QPoint (0, mIconSize), + QString::fromUtf8 (stream.str().c_str())); + } + + { + std::ostringstream stream; + stream << max.first << ", " << max.second; + + QString text = QString::fromUtf8 (stream.str().c_str()); + + painter.drawText (QPoint (mIconSize-metrics.width (text), fontSize), text); + } + } + } + + QIcon icon (image); + setIcon (icon); +} \ No newline at end of file diff --git a/apps/opencs/view/world/scenetoolgrid.hpp b/apps/opencs/view/world/scenetoolgrid.hpp new file mode 100644 index 000000000..917df2a16 --- /dev/null +++ b/apps/opencs/view/world/scenetoolgrid.hpp @@ -0,0 +1,29 @@ +#ifndef CSV_WORLD_SCENETOOL_GRID_H +#define CSV_WORLD_SCENETOOL_GRID_H + +#include "scenetool.hpp" + +namespace CSVWorld +{ + class SceneToolbar; + + ///< \brief Cell grid selector tool + class SceneToolGrid : public SceneTool + { + Q_OBJECT + + int mIconSize; + + public: + + SceneToolGrid (SceneToolbar *parent); + + virtual void showPanel (const QPoint& position); + + public slots: + + void cellIndexChanged (const std::pair& min, const std::pair& max); + }; +} + +#endif From 0c957a3cde6c93830c587b3bb6e5a4687f6184be Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Thu, 3 Apr 2014 14:53:31 -0400 Subject: [PATCH 47/85] Added witnesses to the mix --- apps/openmw/mwmechanics/actors.cpp | 44 ++++++++++++------- apps/openmw/mwmechanics/creaturestats.cpp | 12 ++++- apps/openmw/mwmechanics/creaturestats.hpp | 5 +++ .../mwmechanics/mechanicsmanagerimp.cpp | 11 ++++- apps/openmw/mwworld/player.cpp | 13 +++++- apps/openmw/mwworld/player.hpp | 6 +++ 6 files changed, 70 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 11469c1a9..6d9f23aa2 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -719,9 +719,9 @@ namespace MWMechanics void Actors::updateCrimePersuit(const MWWorld::Ptr& ptr, float duration) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - int bounty = player.getClass().getNpcStats(player).getBounty(); + CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); - // TODO: Move me! I shouldn't be here... + /// \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()) * @@ -729,30 +729,40 @@ namespace MWMechanics if (ptr != player) { - CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); - // Alarmed or not, I will kill you because you've commited heinous against the empire - if ((!creatureStats.isAlarmed() || creatureStats.isAlarmed()) && - ptr.getClass().isClass(ptr, "Guard") && bounty >= cutoff && !creatureStats.isHostile()) - creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player))); - else if (creatureStats.isAlarmed()) + // If I'm a guard and I'm not hostile + if (ptr.getClass().isClass(ptr, "Guard") && !creatureStats.isHostile()) + { + // 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); + } + } + + // if I was a witness to a crime + if (creatureStats.getCrimeId() != -1) { - MWBase::Environment::get().getDialogueManager()->say(ptr, "Thief"); - if(bounty == 0) + if(player.getClass().getNpcStats(player).getBounty() == 0) { creatureStats.setAlarmed(false); creatureStats.setHostile(false); if (ptr.getClass().isClass(ptr, "Guard")) creatureStats.getAiSequence().stopPersue(); creatureStats.getAiSequence().stopCombat(); - + creatureStats.setCrimeId(-1); } - - if (ptr.getClass().isClass(ptr, "Guard") && !creatureStats.isHostile()) - creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player))); - else if (!creatureStats.isHostile()) + else if (creatureStats.isAlarmed()) { - creatureStats.getAiSequence().stack(AiCombat(player)); - creatureStats.setHostile(true); + if (ptr.getClass().isClass(ptr, "Guard") && !creatureStats.isHostile()) + creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player))); + else if (!creatureStats.isHostile()) + { + creatureStats.getAiSequence().stack(AiCombat(player)); + creatureStats.setHostile(true); + } } } } diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index a358917de..17d36be79 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -15,7 +15,7 @@ namespace MWMechanics : mLevel (0), mDead (false), mDied (false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), mAttacked (false), mHostile (false), mAssaulted(false), - mAttackingOrSpell(false), + mAttackingOrSpell(false), mCrimeId(-1), mIsWerewolf(false), mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f) @@ -326,6 +326,16 @@ namespace MWMechanics mAssaulted = assaulted; } + int CreatureStats::getCrimeId() const + { + return mCrimeId; + } + + void CreatureStats::setCrimeId (int id) + { + mCrimeId = id; + } + bool CreatureStats::getCreatureTargetted() const { std::string target; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 7db895dab..67afd9f25 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -40,6 +40,7 @@ namespace MWMechanics bool mAttacked; bool mHostile; bool mAssaulted; + int mCrimeId; bool mAttackingOrSpell; bool mKnockdown; bool mHitRecovery; @@ -191,6 +192,10 @@ namespace MWMechanics void setAssaulted (bool assaulted); + int getCrimeId() const; + + void setCrimeId (int id); + bool getCreatureTargetted() const; float getEvasion() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 39cf63cd0..54622c0b4 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -810,6 +810,7 @@ namespace MWMechanics return false; const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + MWWorld::Player player = MWBase::Environment::get().getWorld()->getPlayer(); // What amount of alarm did this crime generate? int alarm; @@ -858,13 +859,19 @@ namespace MWMechanics if (*it1 == ptr) // Not the player continue; - // was the witness alarmed? + // Will the witness be affected by the crime? CreatureStats& creatureStats1 = MWWorld::Class::get(*it1).getCreatureStats(*it1); if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) + { creatureStats1.setAlarmed(true); + creatureStats1.setCrimeId(player.getWitnessTotal()); + player.addWitness(); + } } break; // Someone saw the crime and everyone has been told - } + } + else if (type == OT_Assault) + creatureStats.setAlarmed(true); } } if (reported) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 6d551ecf1..7c576960f 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -28,8 +28,9 @@ namespace MWWorld : mCellStore(0), mLastKnownExteriorPosition(0,0,0), mAutoMove(false), - mForwardBackward (0), + mForwardBackward(0), mTeleported(false), + mWitnessTotal(0), mMarkedCell(NULL) { mPlayer.mBase = player; @@ -65,6 +66,16 @@ namespace MWWorld return mSign; } + void Player::addWitness() + { + mWitnessTotal++; + } + + int Player::getWitnessTotal() const + { + return mWitnessTotal; + } + void Player::setDrawState (MWMechanics::DrawState_ state) { MWWorld::Ptr ptr = getPlayer(); diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 9d3fbbeec..71c231481 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -41,6 +41,8 @@ namespace MWWorld bool mAutoMove; int mForwardBackward; bool mTeleported; + + int mWitnessTotal; public: @@ -65,6 +67,10 @@ namespace MWWorld void setBirthSign(const std::string &sign); + void addWitness(); + + int getWitnessTotal() const; + const std::string &getBirthSign() const; void setDrawState (MWMechanics::DrawState_ state); From 040d4f8fc4495be3316c5ad47928c4eed6138f4a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 4 Apr 2014 06:13:47 +1100 Subject: [PATCH 48/85] Move PathgridGraph into separate files. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 335 ----------------------- apps/openmw/mwmechanics/pathfinding.hpp | 60 ---- apps/openmw/mwmechanics/pathgrid.cpp | 348 ++++++++++++++++++++++++ apps/openmw/mwmechanics/pathgrid.hpp | 75 +++++ apps/openmw/mwworld/cellstore.cpp | 19 +- apps/openmw/mwworld/cellstore.hpp | 2 +- 7 files changed, 426 insertions(+), 415 deletions(-) create mode 100644 apps/openmw/mwmechanics/pathgrid.cpp create mode 100644 apps/openmw/mwmechanics/pathgrid.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index d1f7c45f3..ea4756ac4 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -68,7 +68,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 - aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting + aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting disease pickpocket levelledlist combat steering ) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 9fcd335e4..a4166d83b 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -1,7 +1,5 @@ #include "pathfinding.hpp" -#include - #include "OgreMath.h" #include "OgreVector3.h" @@ -37,44 +35,6 @@ namespace return sqrt(x * x + y * y + z * z); } - // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html - // - // One of the smallest cost in Seyda Neen is between points 77 & 78: - // pt x y - // 77 = 8026, 4480 - // 78 = 7986, 4218 - // - // Euclidean distance is about 262 (ignoring z) and Manhattan distance is 300 - // (again ignoring z). Using a value of about 300 for D seems like a reasonable - // starting point for experiments. If in doubt, just use value 1. - // - // The distance between 3 & 4 are pretty small, too. - // 3 = 5435, 223 - // 4 = 5948, 193 - // - // Approx. 514 Euclidean distance and 533 Manhattan distance. - // - float manhattan(const ESM::Pathgrid::Point a, const ESM::Pathgrid::Point b) - { - return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ)); - } - - // Choose a heuristics - Note that these may not be the best for directed - // graphs with non-uniform edge costs. - // - // distance: - // - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2) - // - slower but more accurate - // - // Manhattan: - // - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z| - // - faster but not the shortest path - float costAStar(const ESM::Pathgrid::Point a, const ESM::Pathgrid::Point b) - { - //return distance(a, b); - return manhattan(a, b); - } - // Slightly cheaper version for comparisons. // Caller needs to be careful for very short distances (i.e. less than 1) // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 @@ -368,299 +328,4 @@ namespace MWMechanics } - // TODO: Any multi threading concerns? - PathgridGraph::PathgridGraph() - : mCell(NULL) - , mIsGraphConstructed(false) - , mPathgrid(NULL) - , mGraph(0) - , mSCCId(0) - , mSCCIndex(0) - { - } - - /* - * mGraph is populated with the cost of each allowed edge. - * - * The data structure is based on the code in buildPath2() but modified. - * Please check git history if interested. - * - * mGraph[v].edges[i].index = w - * - * v = point index of location "from" - * i = index of edges from point v - * w = point index of location "to" - * - * - * Example: (notice from p(0) to p(2) is not allowed in this example) - * - * mGraph[0].edges[0].index = 1 - * .edges[1].index = 3 - * - * mGraph[1].edges[0].index = 0 - * .edges[1].index = 2 - * .edges[2].index = 3 - * - * mGraph[2].edges[0].index = 1 - * - * (etc, etc) - * - * - * low - * cost - * p(0) <---> p(1) <------------> p(2) - * ^ ^ - * | | - * | +-----> p(3) - * +----------------> - * high cost - */ - bool PathgridGraph::initPathgridGraph(const ESM::Cell* cell) - { - if(!cell) - return false; - - mCell = cell; - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); - - if(!mPathgrid) - return false; - - mGraph.resize(mPathgrid->mPoints.size()); - for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) - { - ConnectedPoint neighbour; - neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0], - mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]); - // forward path of the edge - neighbour.index = mPathgrid->mEdges[i].mV1; - mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour); - // reverse path of the edge - // NOTE: These are redundant, ESM already contains the required reverse paths - //neighbour.index = mPathgrid->mEdges[i].mV0; - //mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour); - } - buildConnectedPoints(); - mIsGraphConstructed = true; -#if 0 - std::cout << "loading pathgrid " << - +"\""+ mPathgrid->mCell +"\"" - +", "+ std::to_string(mPathgrid->mData.mX) - +", "+ std::to_string(mPathgrid->mData.mY) - << std::endl; -#endif - return true; - } - - // v is the pathgrid point index (some call them vertices) - void PathgridGraph::recursiveStrongConnect(int v) - { - mSCCPoint[v].first = mSCCIndex; // index - mSCCPoint[v].second = mSCCIndex; // lowlink - mSCCIndex++; - mSCCStack.push_back(v); - int w; - - for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) - { - w = mGraph[v].edges[i].index; - if(mSCCPoint[w].first == -1) // not visited - { - recursiveStrongConnect(w); // recurse - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].second); - } - else - { - if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].first); - } - } - - if(mSCCPoint[v].second == mSCCPoint[v].first) - { // new component - do - { - w = mSCCStack.back(); - mSCCStack.pop_back(); - mGraph[w].componentId = mSCCId; - } - while(w != v); - mSCCId++; - } - return; - } - - /* - * mGraph contains the strongly connected component group id's along - * with pre-calculated edge costs. - * - * A cell can have disjointed pathgrids, e.g. Seyda Neen has 3 - * - * mGraph for Seyda Neen will therefore have 3 different values. When - * selecting a random pathgrid point for AiWander, mGraph can be checked - * for quickly finding whether the destination is reachable. - * - * Otherwise, buildPath can automatically select a closest reachable end - * pathgrid point (reachable from the closest start point). - * - * Using Tarjan's algorithm: - * - * mGraph | graph G | - * mSCCPoint | V | derived from mPoints - * mGraph[v].edges | E (for v) | - * mSCCIndex | index | tracking smallest unused index - * mSCCStack | S | - * mGraph[v].edges[i].index | w | - * - */ - void PathgridGraph::buildConnectedPoints() - { - // both of these are set to zero in the constructor - //mSCCId = 0; // how many strongly connected components in this cell - //mSCCIndex = 0; - int pointsSize = mPathgrid->mPoints.size(); - mSCCPoint.resize(pointsSize, std::pair (-1, -1)); - mSCCStack.reserve(pointsSize); - - for(int v = 0; v < static_cast (pointsSize); v++) - { - if(mSCCPoint[v].first == -1) // undefined (haven't visited) - recursiveStrongConnect(v); - } -#if 0 - std::cout << "components: " << std::to_string(mSCCId) - +", "+ mPathgrid->mCell - << std::endl; -#endif - } - - bool PathgridGraph::isPointConnected(const int start, const int end) const - { - return (mGraph[start].componentId == mGraph[end].componentId); - } - - /* - * NOTE: Based on buildPath2(), please check git history if interested - * Should consider using a 3rd party library version (e.g. boost) - * - * Find the shortest path to the target goal using a well known algorithm. - * Uses mGraph which has pre-computed costs for allowed edges. It is assumed - * that mGraph is already constructed. - * - * Should be possible to make this MT safe. - * - * Returns path (a list of pathgrid point indexes) which may be empty. - * - * Input params: - * start, goal - pathgrid point indexes (for this cell) - * isExterior - used to determine whether to convert to world co-ordinates - * - * Variables: - * openset - point indexes to be traversed, lowest cost at the front - * closedset - point indexes already traversed - * gScore - past accumulated costs vector indexed by point index - * fScore - future estimated costs vector indexed by point index - * - * TODO: An intersting exercise might be to cache the paths created for a - * start/goal pair. To cache the results the paths need to be in - * pathgrid points form (currently they are converted to world - * co-ordinates). Essentially trading speed w/ memory. - */ - std::list PathgridGraph::aStarSearch(const int start, - const int goal, - bool isExterior) const - { - std::list path; - if(!isPointConnected(start, goal)) - { - return path; // there is no path, return an empty path - } - - int graphSize = mGraph.size(); - std::vector gScore; - gScore.resize(graphSize, -1); - std::vector fScore; - fScore.resize(graphSize, -1); - std::vector graphParent; - graphParent.resize(graphSize, -1); - - // gScore & fScore keep costs for each pathgrid point in mPoints - gScore[start] = 0; - fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]); - - std::list openset; - std::list closedset; - openset.push_back(start); - - int current = -1; - - while(!openset.empty()) - { - current = openset.front(); // front has the lowest cost - openset.pop_front(); - - if(current == goal) - break; - - closedset.push_back(current); // remember we've been here - - // check all edges for the current point index - for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) - { - if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) == - closedset.end()) - { - // not in closedset - i.e. have not traversed this edge destination - int dest = mGraph[current].edges[j].index; - float tentative_g = gScore[current] + mGraph[current].edges[j].cost; - bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); - if(!isInOpenSet - || tentative_g < gScore[dest]) - { - graphParent[dest] = current; - gScore[dest] = tentative_g; - fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest], - mPathgrid->mPoints[goal]); - if(!isInOpenSet) - { - // add this edge to openset, lowest cost goes to the front - // TODO: if this causes performance problems a hash table may help - std::list::iterator it = openset.begin(); - for(it = openset.begin(); it!= openset.end(); it++) - { - if(fScore[*it] > fScore[dest]) - break; - } - openset.insert(it, dest); - } - } - } // if in closedset, i.e. traversed this edge already, try the next edge - } - } - - if(current != goal) - return path; // for some reason couldn't build a path - - // reconstruct path to return, using world co-ordinates - float xCell = 0; - float yCell = 0; - if (isExterior) - { - xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; - yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; - } - - while(graphParent[current] != -1) - { - ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; - pt.mX += xCell; - pt.mY += yCell; - path.push_front(pt); - current = graphParent[current]; - } - return path; - } } - diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index fddb293d9..eb093ad69 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -2,7 +2,6 @@ #define GAME_MWMECHANICS_PATHFINDING_H #include -#include #include #include @@ -83,65 +82,6 @@ namespace MWMechanics const ESM::Pathgrid *mPathgrid; const MWWorld::CellStore* mCell; }; - - class PathgridGraph - { - public: - PathgridGraph(); - - bool isGraphConstructed() const - { - return mIsGraphConstructed; - }; - - bool initPathgridGraph(const ESM::Cell *cell); - - // returns true if end point is strongly connected (i.e. reachable - // from start point) both start and end are pathgrid point indexes - bool isPointConnected(const int start, const int end) const; - - // isOutside is used whether to convert path to world co-ordinates - std::list aStarSearch(const int start, const int end, - const bool isOutside) const; - private: - - const ESM::Cell *mCell; - const ESM::Pathgrid *mPathgrid; - - struct ConnectedPoint // edge - { - int index; // pathgrid point index of neighbour - float cost; - }; - - struct Node // point - { - int componentId; - std::vector edges; // neighbours - }; - - // componentId is an integer indicating the groups of connected - // pathgrid points (all connected points will have the same value) - // - // In Seyda Neen there are 3: - // - // 52, 53 and 54 are one set (enclosed yard) - // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office) - // all other pathgrid points are the third set - // - std::vector mGraph; - bool mIsGraphConstructed; - - // variables used to calculate connected components - int mSCCId; - int mSCCIndex; - std::vector mSCCStack; - typedef std::pair VPair; // first is index, second is lowlink - std::vector mSCCPoint; - // methods used to calculate connected components - void recursiveStrongConnect(int v); - void buildConnectedPoints(); - }; } #endif diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp new file mode 100644 index 000000000..5580e5d3f --- /dev/null +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -0,0 +1,348 @@ +#include "pathgrid.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/cellstore.hpp" + +namespace +{ + // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html + // + // One of the smallest cost in Seyda Neen is between points 77 & 78: + // pt x y + // 77 = 8026, 4480 + // 78 = 7986, 4218 + // + // Euclidean distance is about 262 (ignoring z) and Manhattan distance is 300 + // (again ignoring z). Using a value of about 300 for D seems like a reasonable + // starting point for experiments. If in doubt, just use value 1. + // + // The distance between 3 & 4 are pretty small, too. + // 3 = 5435, 223 + // 4 = 5948, 193 + // + // Approx. 514 Euclidean distance and 533 Manhattan distance. + // + float manhattan(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) + { + return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ)); + } + + // Choose a heuristics - Note that these may not be the best for directed + // graphs with non-uniform edge costs. + // + // distance: + // - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2) + // - slower but more accurate + // + // Manhattan: + // - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z| + // - faster but not the shortest path + float costAStar(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) + { + //return distance(a, b); + return manhattan(a, b); + } +} + +namespace MWMechanics +{ + PathgridGraph::PathgridGraph() + : mCell(NULL) + , mIsGraphConstructed(false) + , mPathgrid(NULL) + , mGraph(0) + , mSCCId(0) + , mSCCIndex(0) + { + } + + /* + * mGraph is populated with the cost of each allowed edge. + * + * The data structure is based on the code in buildPath2() but modified. + * Please check git history if interested. + * + * mGraph[v].edges[i].index = w + * + * v = point index of location "from" + * i = index of edges from point v + * w = point index of location "to" + * + * + * Example: (notice from p(0) to p(2) is not allowed in this example) + * + * mGraph[0].edges[0].index = 1 + * .edges[1].index = 3 + * + * mGraph[1].edges[0].index = 0 + * .edges[1].index = 2 + * .edges[2].index = 3 + * + * mGraph[2].edges[0].index = 1 + * + * (etc, etc) + * + * + * low + * cost + * p(0) <---> p(1) <------------> p(2) + * ^ ^ + * | | + * | +-----> p(3) + * +----------------> + * high cost + */ + bool PathgridGraph::load(const ESM::Cell* cell) + { + if(!cell) + return false; + + mCell = cell; + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); + + if(!mPathgrid) + return false; + + if(mIsGraphConstructed) + return true; + + mGraph.resize(mPathgrid->mPoints.size()); + for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) + { + ConnectedPoint neighbour; + neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0], + mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]); + // forward path of the edge + neighbour.index = mPathgrid->mEdges[i].mV1; + mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour); + // reverse path of the edge + // NOTE: These are redundant, ESM already contains the required reverse paths + //neighbour.index = mPathgrid->mEdges[i].mV0; + //mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour); + } + buildConnectedPoints(); + mIsGraphConstructed = true; +#if 0 + std::cout << "loading pathgrid " << + +"\""+ mPathgrid->mCell +"\"" + +", "+ std::to_string(mPathgrid->mData.mX) + +", "+ std::to_string(mPathgrid->mData.mY) + << std::endl; +#endif + return true; + } + + // v is the pathgrid point index (some call them vertices) + void PathgridGraph::recursiveStrongConnect(int v) + { + mSCCPoint[v].first = mSCCIndex; // index + mSCCPoint[v].second = mSCCIndex; // lowlink + mSCCIndex++; + mSCCStack.push_back(v); + int w; + + for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) + { + w = mGraph[v].edges[i].index; + if(mSCCPoint[w].first == -1) // not visited + { + recursiveStrongConnect(w); // recurse + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].second); + } + else + { + if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].first); + } + } + + if(mSCCPoint[v].second == mSCCPoint[v].first) + { // new component + do + { + w = mSCCStack.back(); + mSCCStack.pop_back(); + mGraph[w].componentId = mSCCId; + } + while(w != v); + mSCCId++; + } + return; + } + + /* + * mGraph contains the strongly connected component group id's along + * with pre-calculated edge costs. + * + * A cell can have disjointed pathgrids, e.g. Seyda Neen has 3 + * + * mGraph for Seyda Neen will therefore have 3 different values. When + * selecting a random pathgrid point for AiWander, mGraph can be checked + * for quickly finding whether the destination is reachable. + * + * Otherwise, buildPath can automatically select a closest reachable end + * pathgrid point (reachable from the closest start point). + * + * Using Tarjan's algorithm: + * + * mGraph | graph G | + * mSCCPoint | V | derived from mPoints + * mGraph[v].edges | E (for v) | + * mSCCIndex | index | tracking smallest unused index + * mSCCStack | S | + * mGraph[v].edges[i].index | w | + * + */ + void PathgridGraph::buildConnectedPoints() + { + // both of these are set to zero in the constructor + //mSCCId = 0; // how many strongly connected components in this cell + //mSCCIndex = 0; + int pointsSize = mPathgrid->mPoints.size(); + mSCCPoint.resize(pointsSize, std::pair (-1, -1)); + mSCCStack.reserve(pointsSize); + + for(int v = 0; v < static_cast (pointsSize); v++) + { + if(mSCCPoint[v].first == -1) // undefined (haven't visited) + recursiveStrongConnect(v); + } +#if 0 + std::cout << "components: " << std::to_string(mSCCId) + +", "+ mPathgrid->mCell + << std::endl; +#endif + } + + bool PathgridGraph::isPointConnected(const int start, const int end) const + { + return (mGraph[start].componentId == mGraph[end].componentId); + } + + /* + * NOTE: Based on buildPath2(), please check git history if interested + * Should consider using a 3rd party library version (e.g. boost) + * + * Find the shortest path to the target goal using a well known algorithm. + * Uses mGraph which has pre-computed costs for allowed edges. It is assumed + * that mGraph is already constructed. + * + * Should be possible to make this MT safe. + * + * Returns path (a list of pathgrid point indexes) which may be empty. + * + * Input params: + * start, goal - pathgrid point indexes (for this cell) + * isExterior - used to determine whether to convert to world co-ordinates + * + * Variables: + * openset - point indexes to be traversed, lowest cost at the front + * closedset - point indexes already traversed + * gScore - past accumulated costs vector indexed by point index + * fScore - future estimated costs vector indexed by point index + * + * TODO: An intersting exercise might be to cache the paths created for a + * start/goal pair. To cache the results the paths need to be in + * pathgrid points form (currently they are converted to world + * co-ordinates). Essentially trading speed w/ memory. + */ + std::list PathgridGraph::aStarSearch(const int start, + const int goal, + bool isExterior) const + { + std::list path; + if(!isPointConnected(start, goal)) + { + return path; // there is no path, return an empty path + } + + int graphSize = mGraph.size(); + std::vector gScore; + gScore.resize(graphSize, -1); + std::vector fScore; + fScore.resize(graphSize, -1); + std::vector graphParent; + graphParent.resize(graphSize, -1); + + // gScore & fScore keep costs for each pathgrid point in mPoints + gScore[start] = 0; + fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]); + + std::list openset; + std::list closedset; + openset.push_back(start); + + int current = -1; + + while(!openset.empty()) + { + current = openset.front(); // front has the lowest cost + openset.pop_front(); + + if(current == goal) + break; + + closedset.push_back(current); // remember we've been here + + // check all edges for the current point index + for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) + { + if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) == + closedset.end()) + { + // not in closedset - i.e. have not traversed this edge destination + int dest = mGraph[current].edges[j].index; + float tentative_g = gScore[current] + mGraph[current].edges[j].cost; + bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); + if(!isInOpenSet + || tentative_g < gScore[dest]) + { + graphParent[dest] = current; + gScore[dest] = tentative_g; + fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest], + mPathgrid->mPoints[goal]); + if(!isInOpenSet) + { + // add this edge to openset, lowest cost goes to the front + // TODO: if this causes performance problems a hash table may help + std::list::iterator it = openset.begin(); + for(it = openset.begin(); it!= openset.end(); it++) + { + if(fScore[*it] > fScore[dest]) + break; + } + openset.insert(it, dest); + } + } + } // if in closedset, i.e. traversed this edge already, try the next edge + } + } + + if(current != goal) + return path; // for some reason couldn't build a path + + // reconstruct path to return, using world co-ordinates + float xCell = 0; + float yCell = 0; + if (isExterior) + { + xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; + yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; + } + + while(graphParent[current] != -1) + { + ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); + current = graphParent[current]; + } + return path; + } +} + diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp new file mode 100644 index 000000000..9f3a997c6 --- /dev/null +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -0,0 +1,75 @@ +#ifndef GAME_MWMECHANICS_PATHGRID_H +#define GAME_MWMECHANICS_PATHGRID_H + +#include +#include + +namespace ESM +{ + class Cell; +} + +namespace MWWorld +{ + class CellStore; +} + +namespace MWMechanics +{ + class PathgridGraph + { + public: + PathgridGraph(); + + bool load(const ESM::Cell *cell); + + // returns true if end point is strongly connected (i.e. reachable + // from start point) both start and end are pathgrid point indexes + bool isPointConnected(const int start, const int end) const; + + // isOutside is used whether to convert path to world co-ordinates + std::list aStarSearch(const int start, + const int end, + const bool isOutside) const; + private: + + const ESM::Cell *mCell; + const ESM::Pathgrid *mPathgrid; + + struct ConnectedPoint // edge + { + int index; // pathgrid point index of neighbour + float cost; + }; + + struct Node // point + { + int componentId; + std::vector edges; // neighbours + }; + + // componentId is an integer indicating the groups of connected + // pathgrid points (all connected points will have the same value) + // + // In Seyda Neen there are 3: + // + // 52, 53 and 54 are one set (enclosed yard) + // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office) + // all other pathgrid points are the third set + // + std::vector mGraph; + bool mIsGraphConstructed; + + // variables used to calculate connected components + int mSCCId; + int mSCCIndex; + std::vector mSCCStack; + typedef std::pair VPair; // first is index, second is lowlink + std::vector mSCCPoint; + // methods used to calculate connected components + void recursiveStrongConnect(int v); + void buildConnectedPoints(); + }; +} + +#endif diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index dd105a665..d39f22c3f 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -363,6 +363,7 @@ namespace MWWorld loadRefs (store, esm); mState = State_Loaded; + mPathgridGraph.load(mCell); } } @@ -683,14 +684,6 @@ namespace MWWorld bool CellStore::isPointConnected(const int start, const int end) const { - if(!mPathgridGraph.isGraphConstructed()) - { - // Ugh... there must be a better way... - MWMechanics::PathgridGraph *p = const_cast (&mPathgridGraph); - - if(!p->initPathgridGraph(mCell)) - return false; - } return mPathgridGraph.isPointConnected(start, end); } @@ -698,16 +691,6 @@ namespace MWWorld std::list CellStore::aStarSearch(const int start, const int end, const bool isOutside) const { - if(!mPathgridGraph.isGraphConstructed()) - { - MWMechanics::PathgridGraph *p = const_cast (&mPathgridGraph); - - if(!p->initPathgridGraph(mCell)) - { - std::list path; // empty - return path; - } - } return mPathgridGraph.aStarSearch(start, end, isOutside); } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 3cca993f2..c352779b1 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -8,7 +8,7 @@ #include "esmstore.hpp" #include "cellreflist.hpp" -#include "../mwmechanics/pathfinding.hpp" +#include "../mwmechanics/pathgrid.hpp" namespace ESM { From 5cf8e7e9334f60a10fb0ddabdc25c4a953151388 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 4 Apr 2014 06:16:26 +1100 Subject: [PATCH 49/85] Remove logging. --- apps/openmw/mwmechanics/pathgrid.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index 5580e5d3f..f8b24fa1a 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -124,13 +124,6 @@ namespace MWMechanics } buildConnectedPoints(); mIsGraphConstructed = true; -#if 0 - std::cout << "loading pathgrid " << - +"\""+ mPathgrid->mCell +"\"" - +", "+ std::to_string(mPathgrid->mData.mX) - +", "+ std::to_string(mPathgrid->mData.mY) - << std::endl; -#endif return true; } @@ -211,11 +204,6 @@ namespace MWMechanics if(mSCCPoint[v].first == -1) // undefined (haven't visited) recursiveStrongConnect(v); } -#if 0 - std::cout << "components: " << std::to_string(mSCCId) - +", "+ mPathgrid->mCell - << std::endl; -#endif } bool PathgridGraph::isPointConnected(const int start, const int end) const From 70919ba60ae596bab066e34bfb860ca5a4aa539e Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Thu, 3 Apr 2014 16:13:14 -0400 Subject: [PATCH 50/85] Removed witnesses and minor changes --- apps/openmw/mwmechanics/actors.cpp | 19 +++++++++------ apps/openmw/mwmechanics/creaturestats.cpp | 24 ++----------------- apps/openmw/mwmechanics/creaturestats.hpp | 9 ------- .../mwmechanics/mechanicsmanagerimp.cpp | 11 ++------- apps/openmw/mwworld/player.cpp | 11 --------- apps/openmw/mwworld/player.hpp | 6 ----- 6 files changed, 16 insertions(+), 64 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6d9f23aa2..56f24a747 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -739,11 +739,12 @@ namespace MWMechanics { creatureStats.getAiSequence().stack(AiCombat(player)); creatureStats.setHostile(true); + creatureStats.setAlarmed(true); } } // if I was a witness to a crime - if (creatureStats.getCrimeId() != -1) + if (creatureStats.isAlarmed()) { if(player.getClass().getNpcStats(player).getBounty() == 0) { @@ -752,19 +753,23 @@ namespace MWMechanics if (ptr.getClass().isClass(ptr, "Guard")) creatureStats.getAiSequence().stopPersue(); creatureStats.getAiSequence().stopCombat(); - creatureStats.setCrimeId(-1); } - else if (creatureStats.isAlarmed()) + else if (!creatureStats.isHostile()) { - if (ptr.getClass().isClass(ptr, "Guard") && !creatureStats.isHostile()) + if (ptr.getClass().isClass(ptr, "Guard")) creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player))); - else if (!creatureStats.isHostile()) - { + 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); + } } } diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 17d36be79..feed8d182 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -14,8 +14,8 @@ namespace MWMechanics CreatureStats::CreatureStats() : mLevel (0), mDead (false), mDied (false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), - mAttacked (false), mHostile (false), mAssaulted(false), - mAttackingOrSpell(false), mCrimeId(-1), + mAttacked (false), mHostile (false), + mAttackingOrSpell(false), mIsWerewolf(false), mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f) @@ -316,26 +316,6 @@ namespace MWMechanics mHostile = hostile; } - bool CreatureStats::isAssaulted() const - { - return mAssaulted; - } - - void CreatureStats::setAssaulted (bool assaulted) - { - mAssaulted = assaulted; - } - - int CreatureStats::getCrimeId() const - { - return mCrimeId; - } - - void CreatureStats::setCrimeId (int id) - { - mCrimeId = id; - } - bool CreatureStats::getCreatureTargetted() const { std::string target; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 67afd9f25..97bcd719c 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -40,7 +40,6 @@ namespace MWMechanics bool mAttacked; bool mHostile; bool mAssaulted; - int mCrimeId; bool mAttackingOrSpell; bool mKnockdown; bool mHitRecovery; @@ -188,14 +187,6 @@ namespace MWMechanics void setHostile (bool hostile); - bool isAssaulted() const; - - void setAssaulted (bool assaulted); - - int getCrimeId() const; - - void setCrimeId (int id); - bool getCreatureTargetted() const; float getEvasion() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 54622c0b4..9119b3ab6 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -839,9 +839,8 @@ namespace MWMechanics CreatureStats& creatureStats = MWWorld::Class::get(*it).getCreatureStats(*it); - // Was the crime seen or the victim assulted? - if ( ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) || - type == OT_Assault) + // Was the crime seen? + if ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) { // Say something! // TODO: Add more messages @@ -862,16 +861,10 @@ namespace MWMechanics // Will the witness be affected by the crime? CreatureStats& creatureStats1 = MWWorld::Class::get(*it1).getCreatureStats(*it1); if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) - { creatureStats1.setAlarmed(true); - creatureStats1.setCrimeId(player.getWitnessTotal()); - player.addWitness(); - } } break; // Someone saw the crime and everyone has been told } - else if (type == OT_Assault) - creatureStats.setAlarmed(true); } } if (reported) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 7c576960f..bfeca9653 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -30,7 +30,6 @@ namespace MWWorld mAutoMove(false), mForwardBackward(0), mTeleported(false), - mWitnessTotal(0), mMarkedCell(NULL) { mPlayer.mBase = player; @@ -66,16 +65,6 @@ namespace MWWorld return mSign; } - void Player::addWitness() - { - mWitnessTotal++; - } - - int Player::getWitnessTotal() const - { - return mWitnessTotal; - } - void Player::setDrawState (MWMechanics::DrawState_ state) { MWWorld::Ptr ptr = getPlayer(); diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 71c231481..9d3fbbeec 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -41,8 +41,6 @@ namespace MWWorld bool mAutoMove; int mForwardBackward; bool mTeleported; - - int mWitnessTotal; public: @@ -67,10 +65,6 @@ namespace MWWorld void setBirthSign(const std::string &sign); - void addWitness(); - - int getWitnessTotal() const; - const std::string &getBirthSign() const; void setDrawState (MWMechanics::DrawState_ state); From f59226265ad1b745d21747ce89b11de5a13b6d7d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 4 Apr 2014 18:10:06 +1100 Subject: [PATCH 51/85] Remove redundant parameter from aStarSearch. Also update some comments. --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- apps/openmw/mwmechanics/pathgrid.cpp | 11 ++++++----- apps/openmw/mwmechanics/pathgrid.hpp | 4 ++-- apps/openmw/mwworld/cellstore.cpp | 6 ++---- apps/openmw/mwworld/cellstore.hpp | 3 +-- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index a4166d83b..730b8cb92 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -224,7 +224,7 @@ namespace MWMechanics // this shouldn't really happen, but just in case if(endNode.first != -1) { - mPath = mCell->aStarSearch(startNode, endNode.first, mCell->isExterior()); + mPath = mCell->aStarSearch(startNode, endNode.first); if(!mPath.empty()) { diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index f8b24fa1a..83ee86db5 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -55,6 +55,7 @@ namespace MWMechanics , mGraph(0) , mSCCId(0) , mSCCIndex(0) + , mIsExterior(0) { } @@ -100,6 +101,7 @@ namespace MWMechanics return false; mCell = cell; + mIsExterior = cell->isExterior(); mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); if(!mPathgrid) @@ -221,11 +223,11 @@ namespace MWMechanics * * Should be possible to make this MT safe. * - * Returns path (a list of pathgrid point indexes) which may be empty. + * Returns path which may be empty. path contains pathgrid points in local + * cell co-ordinates (indoors) or world co-ordinates (external). * * Input params: * start, goal - pathgrid point indexes (for this cell) - * isExterior - used to determine whether to convert to world co-ordinates * * Variables: * openset - point indexes to be traversed, lowest cost at the front @@ -239,8 +241,7 @@ namespace MWMechanics * co-ordinates). Essentially trading speed w/ memory. */ std::list PathgridGraph::aStarSearch(const int start, - const int goal, - bool isExterior) const + const int goal) const { std::list path; if(!isPointConnected(start, goal)) @@ -316,7 +317,7 @@ namespace MWMechanics // reconstruct path to return, using world co-ordinates float xCell = 0; float yCell = 0; - if (isExterior) + if (mIsExterior) { xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index 9f3a997c6..372f6bd2d 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -29,12 +29,12 @@ namespace MWMechanics // isOutside is used whether to convert path to world co-ordinates std::list aStarSearch(const int start, - const int end, - const bool isOutside) const; + const int end) const; private: const ESM::Cell *mCell; const ESM::Pathgrid *mPathgrid; + bool mIsExterior; struct ConnectedPoint // edge { diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index d39f22c3f..6bc7657e4 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -685,12 +685,10 @@ namespace MWWorld bool CellStore::isPointConnected(const int start, const int end) const { return mPathgridGraph.isPointConnected(start, end); - } - std::list CellStore::aStarSearch(const int start, const int end, - const bool isOutside) const + std::list CellStore::aStarSearch(const int start, const int end) const { - return mPathgridGraph.aStarSearch(start, end, isOutside); + return mPathgridGraph.aStarSearch(start, end); } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index c352779b1..b970afe1b 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -145,8 +145,7 @@ namespace MWWorld bool isPointConnected(const int start, const int end) const; - std::list aStarSearch(const int start, const int end, - const bool isOutside) const; + std::list aStarSearch(const int start, const int end) const; private: From 28f7c42fb77536f84e9875e8e4bc5284d75f6aa3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 4 Apr 2014 18:17:42 +1100 Subject: [PATCH 52/85] One more comment fix. --- apps/openmw/mwmechanics/pathgrid.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index 372f6bd2d..ac545efbc 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -27,7 +27,9 @@ namespace MWMechanics // from start point) both start and end are pathgrid point indexes bool isPointConnected(const int start, const int end) const; - // isOutside is used whether to convert path to world co-ordinates + // the input parameters are pathgrid point indexes + // the output list is in local (internal cells) or world (external + // cells) co-ordinates std::list aStarSearch(const int start, const int end) const; private: From df5cbe5dec98b1c14cd4aa8916d692b40c5b18f1 Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Fri, 4 Apr 2014 08:10:35 -0400 Subject: [PATCH 53/85] Minor changes --- apps/openmw/mwmechanics/actors.cpp | 1 + apps/openmw/mwmechanics/creaturestats.cpp | 3 ++- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 56f24a747..fcec5a3fb 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -750,6 +750,7 @@ namespace MWMechanics { creatureStats.setAlarmed(false); creatureStats.setHostile(false); + creatureStats.setAttacked(false); if (ptr.getClass().isClass(ptr, "Guard")) creatureStats.getAiSequence().stopPersue(); creatureStats.getAiSequence().stopCombat(); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index feed8d182..f81613ed1 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/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 9119b3ab6..8d546b598 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -840,7 +840,8 @@ namespace MWMechanics CreatureStats& creatureStats = MWWorld::Class::get(*it).getCreatureStats(*it); // Was the crime seen? - if ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) + if ( ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) || + type == OT_Assault ) { // Say something! // TODO: Add more messages @@ -860,7 +861,8 @@ namespace MWMechanics // Will the witness be affected by the crime? CreatureStats& creatureStats1 = MWWorld::Class::get(*it1).getCreatureStats(*it1); - if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) + if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm || + type == OT_Assault) creatureStats1.setAlarmed(true); } break; // Someone saw the crime and everyone has been told From 0fe67b586a6c8156eb598887550b995dce8c7304 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 5 Apr 2014 13:16:13 +0200 Subject: [PATCH 54/85] increased scene toolbar button size --- apps/opencs/view/world/previewsubview.cpp | 2 +- apps/opencs/view/world/scenesubview.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index ac9776d22..e9a30e65d 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -28,7 +28,7 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo else mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), this); - SceneToolbar *toolbar = new SceneToolbar (48, this); + SceneToolbar *toolbar = new SceneToolbar (48+6, this); SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); toolbar->addTool (lightingTool); diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index dedaf014b..d62793cc9 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -35,7 +35,7 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D layout2->setContentsMargins (QMargins (0, 0, 0, 0)); - SceneToolbar *toolbar = new SceneToolbar (48, this); + SceneToolbar *toolbar = new SceneToolbar (48+6, this); SceneToolGrid *gridTool = 0; From 940c88d2ecd6b25a3919596eff3a60b7a7d2d0fe Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Sat, 5 Apr 2014 10:26:14 -0400 Subject: [PATCH 55/85] Cleaned up code, implemented crime ids There is a problem with my game freezing. ToggleAi stops my character --- apps/openmw/mwmechanics/actors.cpp | 57 +++++++++++-------- apps/openmw/mwmechanics/creaturestats.hpp | 4 -- .../mwmechanics/mechanicsmanagerimp.cpp | 35 ++++++------ apps/openmw/mwmechanics/npcstats.cpp | 11 ++++ apps/openmw/mwmechanics/npcstats.hpp | 6 +- apps/openmw/mwscript/miscextensions.cpp | 4 ++ apps/openmw/mwworld/player.cpp | 19 ++++++- apps/openmw/mwworld/player.hpp | 12 ++-- 8 files changed, 95 insertions(+), 53 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index fcec5a3fb..ee30dae6d 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -31,8 +31,6 @@ #include "aifollow.hpp" #include "aipersue.hpp" -#include "../mwbase/dialoguemanager.hpp" //------------------------ - namespace { @@ -718,49 +716,58 @@ namespace MWMechanics void Actors::updateCrimePersuit(const MWWorld::Ptr& ptr, float duration) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); - - /// \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(); - - if (ptr != player) + MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (ptr != playerPtr) { + // get stats of witness + CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); + NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats(ptr); + MWWorld::Player player = MWBase::Environment::get().getWorld()->getPlayer(); + // 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)) + if ( playerPtr.getClass().getNpcStats(playerPtr).getBounty() >= cutoff + && MWBase::Environment::get().getWorld()->getLOS(ptr, playerPtr) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(playerPtr, ptr)) { - creatureStats.getAiSequence().stack(AiCombat(player)); + creatureStats.getAiSequence().stack(AiCombat(playerPtr)); creatureStats.setHostile(true); - creatureStats.setAlarmed(true); + npcStats.setCrimeId(player.getNewCrimeId()); } } // if I was a witness to a crime - if (creatureStats.isAlarmed()) + if (npcStats.getCrimeId() != -1) { - if(player.getClass().getNpcStats(player).getBounty() == 0) + // if you've payed for your crimes and I havent noticed + if(npcStats.getCrimeId() < player.getCrimeId() ) { - creatureStats.setAlarmed(false); - creatureStats.setHostile(false); - creatureStats.setAttacked(false); + // 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))); + creatureStats.getAiSequence().stack(AiPersue(playerPtr.getClass().getId(playerPtr))); else - creatureStats.getAiSequence().stack(AiCombat(player)); + creatureStats.getAiSequence().stack(AiCombat(playerPtr)); creatureStats.setHostile(true); } } @@ -768,7 +775,7 @@ namespace MWMechanics // if I didn't report a crime was I attacked? else if (creatureStats.getAttacked() && !creatureStats.isHostile()) { - creatureStats.getAiSequence().stack(AiCombat(player)); + creatureStats.getAiSequence().stack(AiCombat(playerPtr)); creatureStats.setHostile(true); } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 97bcd719c..5dc59e5ab 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -39,7 +39,6 @@ namespace MWMechanics bool mAlarmed; bool mAttacked; bool mHostile; - bool mAssaulted; bool mAttackingOrSpell; bool mKnockdown; bool mHitRecovery; @@ -176,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 8d546b598..a26520fc7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -828,42 +828,43 @@ namespace MWMechanics // Innocent until proven guilty bool reported = false; - // Find all the NPC's close enough, ie. within fAlarmRadius of the player + // 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); + 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) // Not the player - continue; - - CreatureStats& creatureStats = MWWorld::Class::get(*it).getCreatureStats(*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 ) { - // Say something! // TODO: Add more messages if (type == OT_Theft) - MWBase::Environment::get().getDialogueManager()->say(*it, "Thief"); + MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); + else if (type == OT_Assault) + MWBase::Environment::get().getDialogueManager()->say(*it, "attack"); // Will the witness report the crime? - if (creatureStats.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) + if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { reported = true; + int id = player.getNewCrimeId(); - // Tell everyone else + // Tell everyone, including yourself for (std::vector::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1) { - if (*it1 == ptr) // Not the player - continue; + if (*it1 == ptr) continue; // not the player - // Will the witness be affected by the crime? - CreatureStats& creatureStats1 = MWWorld::Class::get(*it1).getCreatureStats(*it1); - if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm || - type == OT_Assault) - creatureStats1.setAlarmed(true); + // 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 } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 74587a626..4f014102d 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) diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index ad493be3c..0ae596a54 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 a7f31c80b..aa554adc3 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,6 +814,7 @@ namespace MWScript { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty(0); + MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; @@ -823,6 +826,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(); } }; diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index bfeca9653..d39ec2c0f 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -30,7 +30,9 @@ namespace MWWorld mAutoMove(false), mForwardBackward(0), mTeleported(false), - mMarkedCell(NULL) + mMarkedCell(NULL), + mCurrentCrimeId(-1), + mPayedCrimeId(-1) { mPlayer.mBase = player; mPlayer.mRef.mRefID = "player"; @@ -274,4 +276,19 @@ namespace MWWorld return false; } + + int Player::getNewCrimeId() + { + return mCurrentCrimeId++; + } + + void Player::recordCrimeId() + { + mPayedCrimeId = mCurrentCrimeId; + } + + int Player::getCrimeId() const + { + return mCurrentCrimeId; + } } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 9d3fbbeec..7dbaaddb4 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -41,6 +41,9 @@ namespace MWWorld bool mAutoMove; int mForwardBackward; bool mTeleported; + + int mCurrentCrimeId; // the id assigned witnesses + int mPayedCrimeId; // the last id payed off (0 bounty) public: @@ -64,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); @@ -95,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 From a274b48f2f8c136f03a43d2ba62c5771277f52ef Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Sat, 5 Apr 2014 22:45:40 -0400 Subject: [PATCH 56/85] States are saved. Crime is reacted to. Issues where some crime is ignored. Needs a lot more work --- apps/openmw/mwmechanics/actors.cpp | 23 +++++++++---------- .../mwmechanics/mechanicsmanagerimp.cpp | 14 +++++------ apps/openmw/mwworld/player.cpp | 4 ++-- components/esm/npcstats.cpp | 6 +++++ components/esm/npcstats.hpp | 1 + components/esm/player.cpp | 6 +++++ components/esm/player.hpp | 3 +++ 7 files changed, 36 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index ee30dae6d..ca37b152c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -716,13 +716,12 @@ namespace MWMechanics void Actors::updateCrimePersuit(const MWWorld::Ptr& ptr, float duration) { - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (ptr != playerPtr) + 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); - MWWorld::Player player = MWBase::Environment::get().getWorld()->getPlayer(); // If I'm a guard and I'm not hostile if (ptr.getClass().isClass(ptr, "Guard") && !creatureStats.isHostile()) @@ -733,13 +732,13 @@ namespace MWMechanics float(esmStore.get().find("iCrimeThresholdMultiplier")->getInt()) * esmStore.get().find("fCrimeGoldDiscountMult")->getFloat(); // Attack on sight if bounty is greater than the cutoff - if ( playerPtr.getClass().getNpcStats(playerPtr).getBounty() >= cutoff - && MWBase::Environment::get().getWorld()->getLOS(ptr, playerPtr) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(playerPtr, ptr)) + 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(playerPtr)); + creatureStats.getAiSequence().stack(AiCombat(player)); creatureStats.setHostile(true); - npcStats.setCrimeId(player.getNewCrimeId()); + npcStats.setCrimeId( MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() ); } } @@ -747,7 +746,7 @@ namespace MWMechanics if (npcStats.getCrimeId() != -1) { // if you've payed for your crimes and I havent noticed - if(npcStats.getCrimeId() < player.getCrimeId() ) + if( npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() ) { // Calm witness down if (ptr.getClass().isClass(ptr, "Guard")) @@ -765,9 +764,9 @@ namespace MWMechanics else if (!creatureStats.isHostile()) { if (ptr.getClass().isClass(ptr, "Guard")) - creatureStats.getAiSequence().stack(AiPersue(playerPtr.getClass().getId(playerPtr))); + creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player))); else - creatureStats.getAiSequence().stack(AiCombat(playerPtr)); + creatureStats.getAiSequence().stack(AiCombat(player)); creatureStats.setHostile(true); } } @@ -775,7 +774,7 @@ namespace MWMechanics // if I didn't report a crime was I attacked? else if (creatureStats.getAttacked() && !creatureStats.isHostile()) { - creatureStats.getAiSequence().stack(AiCombat(playerPtr)); + creatureStats.getAiSequence().stack(AiCombat(player)); creatureStats.setHostile(true); } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index a26520fc7..d162e1037 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -810,7 +810,6 @@ namespace MWMechanics return false; const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - MWWorld::Player player = MWBase::Environment::get().getWorld()->getPlayer(); // What amount of alarm did this crime generate? int alarm; @@ -842,22 +841,23 @@ namespace MWMechanics if ( ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) || type == OT_Assault ) { - // TODO: Add more messages - if (type == OT_Theft) - MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); - else if (type == OT_Assault) - MWBase::Environment::get().getDialogueManager()->say(*it, "attack"); // Will the witness report the crime? if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { reported = true; - int id = player.getNewCrimeId(); + 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 diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index d39ec2c0f..e8179b9f3 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -279,7 +279,7 @@ namespace MWWorld int Player::getNewCrimeId() { - return mCurrentCrimeId++; + return ++mCurrentCrimeId; } void Player::recordCrimeId() @@ -289,6 +289,6 @@ namespace MWWorld int Player::getCrimeId() const { - return mCurrentCrimeId; + return mPayedCrimeId; } } diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index 531424ab2..80238ad68 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 b3f70db25..504cd0163 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 d5ddc74d0..e41cc535e 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 0d70ee090..377c8547a 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; From ce7aa963717122a1de7d055d06be41e1365ab1d7 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Sun, 6 Apr 2014 22:21:28 +0200 Subject: [PATCH 57/85] Fix visual glitch happening when closing inventory while sneaking (bug #1255) --- apps/openmw/mwrender/renderingmanager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index fa7b17a7c..97283d065 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -364,10 +364,9 @@ void RenderingManager::update (float duration, bool paused) static const int i1stPersonSneakDelta = MWBase::Environment::get().getWorld()->getStore().get() .find("i1stPersonSneakDelta")->getInt(); - if(isSneaking && !(isSwimming || isInAir)) + if(!paused && isSneaking && !(isSwimming || isInAir)) mCamera->setSneakOffset(i1stPersonSneakDelta); - mOcclusionQuery->update(duration); mRendering.update(duration); From 03b3487f1bbb3114e00999e18cc329bbaf961af8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 7 Apr 2014 09:56:36 +0200 Subject: [PATCH 58/85] minor cleanup --- apps/openmw/mwmechanics/pathgrid.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index 83ee86db5..ec647c1cb 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -250,12 +250,9 @@ namespace MWMechanics } int graphSize = mGraph.size(); - std::vector gScore; - gScore.resize(graphSize, -1); - std::vector fScore; - fScore.resize(graphSize, -1); - std::vector graphParent; - graphParent.resize(graphSize, -1); + std::vector gScore (graphSize, -1); + std::vector fScore (graphSize, -1); + std::vector graphParent (graphSize, -1); // gScore & fScore keep costs for each pathgrid point in mPoints gScore[start] = 0; From 324b2743d447bb3d6f2a18dae780ea3783857070 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 7 Apr 2014 10:21:26 +0200 Subject: [PATCH 59/85] removed grid button (discarding the first attempt at a cell selector) --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/scenesubview.cpp | 13 ---- apps/opencs/view/world/scenetoolgrid.cpp | 75 ------------------------ apps/opencs/view/world/scenetoolgrid.hpp | 29 --------- 4 files changed, 1 insertion(+), 118 deletions(-) delete mode 100644 apps/opencs/view/world/scenetoolgrid.cpp delete mode 100644 apps/opencs/view/world/scenetoolgrid.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 05cc93f89..a7a694463 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool - scenetoolmode infocreator scriptedit dialoguesubview previewsubview scenetoolgrid + scenetoolmode infocreator scriptedit dialoguesubview previewsubview ) opencs_units (view/render diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index d62793cc9..4ebaa9352 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -18,7 +18,6 @@ #include "creator.hpp" #include "scenetoolbar.hpp" #include "scenetoolmode.hpp" -#include "scenetoolgrid.hpp" CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id) @@ -37,8 +36,6 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolbar *toolbar = new SceneToolbar (48+6, this); - SceneToolGrid *gridTool = 0; - if (id.getId()=="sys::default") { CSVRender::PagedWorldspaceWidget *widget = new CSVRender::PagedWorldspaceWidget (this); @@ -47,13 +44,6 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), this, SLOT (cellIndexChanged (const std::pair&, const std::pair&))); - - gridTool = new SceneToolGrid (toolbar); - - connect (widget, - SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), - gridTool, - SLOT (cellIndexChanged (const std::pair&, const std::pair&))); } else mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); @@ -64,9 +54,6 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); toolbar->addTool (lightingTool); - if (gridTool) - toolbar->addTool (gridTool); - layout2->addWidget (toolbar, 0); layout2->addWidget (mScene, 1); diff --git a/apps/opencs/view/world/scenetoolgrid.cpp b/apps/opencs/view/world/scenetoolgrid.cpp deleted file mode 100644 index 0769be168..000000000 --- a/apps/opencs/view/world/scenetoolgrid.cpp +++ /dev/null @@ -1,75 +0,0 @@ - -#include "scenetoolgrid.hpp" - -#include - -#include -#include -#include - -#include "scenetoolbar.hpp" - -CSVWorld::SceneToolGrid::SceneToolGrid (SceneToolbar *parent) -: SceneTool (parent), mIconSize (parent->getIconSize()) -{ -} - -void CSVWorld::SceneToolGrid::showPanel (const QPoint& position) -{ - - -} - -void CSVWorld::SceneToolGrid::cellIndexChanged (const std::pair& min, - const std::pair& max) -{ - /// \todo make font size configurable - const int fontSize = 8; - - /// \todo replace with proper icon - QPixmap image (mIconSize, mIconSize); - image.fill (QColor (0, 0, 0, 0)); - - { - QPainter painter (&image); - painter.setPen (Qt::black); - QFont font (QApplication::font().family(), fontSize); - painter.setFont (font); - - QFontMetrics metrics (font); - - if (min==max) - { - // single cell - std::ostringstream stream; - stream << min.first << ", " << min.second; - - QString text = QString::fromUtf8 (stream.str().c_str()); - - painter.drawText (QPoint ((mIconSize-metrics.width (text))/2, mIconSize/2+fontSize/2), - text); - } - else - { - // range - { - std::ostringstream stream; - stream << min.first << ", " << min.second; - painter.drawText (QPoint (0, mIconSize), - QString::fromUtf8 (stream.str().c_str())); - } - - { - std::ostringstream stream; - stream << max.first << ", " << max.second; - - QString text = QString::fromUtf8 (stream.str().c_str()); - - painter.drawText (QPoint (mIconSize-metrics.width (text), fontSize), text); - } - } - } - - QIcon icon (image); - setIcon (icon); -} \ No newline at end of file diff --git a/apps/opencs/view/world/scenetoolgrid.hpp b/apps/opencs/view/world/scenetoolgrid.hpp deleted file mode 100644 index 917df2a16..000000000 --- a/apps/opencs/view/world/scenetoolgrid.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef CSV_WORLD_SCENETOOL_GRID_H -#define CSV_WORLD_SCENETOOL_GRID_H - -#include "scenetool.hpp" - -namespace CSVWorld -{ - class SceneToolbar; - - ///< \brief Cell grid selector tool - class SceneToolGrid : public SceneTool - { - Q_OBJECT - - int mIconSize; - - public: - - SceneToolGrid (SceneToolbar *parent); - - virtual void showPanel (const QPoint& position); - - public slots: - - void cellIndexChanged (const std::pair& min, const std::pair& max); - }; -} - -#endif From 67965ec10cc211469308c443363ea28e25fdf304 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 7 Apr 2014 13:42:00 +0200 Subject: [PATCH 60/85] added CellCoordinates and CellSelection classes --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/cellcoordinates.cpp | 60 +++++++++++++++ apps/opencs/model/world/cellcoordinates.hpp | 42 +++++++++++ apps/opencs/model/world/cellselection.cpp | 83 +++++++++++++++++++++ apps/opencs/model/world/cellselection.hpp | 57 ++++++++++++++ 5 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/model/world/cellcoordinates.cpp create mode 100644 apps/opencs/model/world/cellcoordinates.hpp create mode 100644 apps/opencs/model/world/cellselection.cpp create mode 100644 apps/opencs/model/world/cellselection.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index a7a694463..d9e271498 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -24,7 +24,7 @@ opencs_units (model/world opencs_units_noqt (model/world universalid record commands columnbase scriptcontext cell refidcollection - refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata + refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection ) opencs_hdrs_noqt (model/world diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp new file mode 100644 index 000000000..b1c8441e6 --- /dev/null +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -0,0 +1,60 @@ + +#include "cellcoordinates.hpp" + +#include +#include + +CSMWorld::CellCoordinates::CellCoordinates() : mX (0), mY (0) {} + +CSMWorld::CellCoordinates::CellCoordinates (int x, int y) : mX (x), mY (y) {} + +int CSMWorld::CellCoordinates::getX() const +{ + return mX; +} + +int CSMWorld::CellCoordinates::getY() const +{ + return mY; +} + +CSMWorld::CellCoordinates CSMWorld::CellCoordinates::move (int x, int y) const +{ + return CellCoordinates (mX + x, mY + y); +} + +std::string CSMWorld::CellCoordinates::getId (const std::string& worldspace) const +{ + // we ignore the worldspace for now, since there is only one (will change in 1.1) + std::ostringstream stream; + + stream << "#" << mX << " " << mY; + + return stream.str(); +} + +bool CSMWorld::operator== (const CellCoordinates& left, const CellCoordinates& right) +{ + return left.getX()==right.getX() && left.getY()==right.getY(); +} + +bool CSMWorld::operator!= (const CellCoordinates& left, const CellCoordinates& right) +{ + return !(left==right); +} + +bool CSMWorld::operator< (const CellCoordinates& left, const CellCoordinates& right) +{ + if (left.getX()right.getX()) + return false; + + return left.getY() +#include + +#include + +namespace CSMWorld +{ + class CellCoordinates + { + int mX; + int mY; + + public: + + CellCoordinates(); + + CellCoordinates (int x, int y); + + int getX() const; + + int getY() const; + + CellCoordinates move (int x, int y) const; + ///< Return a copy of *this, moved by the given offset. + + std::string getId (const std::string& worldspace) const; + ///< Return the ID for the cell at these coordinates. + }; + + bool operator== (const CellCoordinates& left, const CellCoordinates& right); + bool operator!= (const CellCoordinates& left, const CellCoordinates& right); + bool operator< (const CellCoordinates& left, const CellCoordinates& right); + + std::ostream& operator<< (std::ostream& stream, const CellCoordinates& coordiantes); +} + +Q_DECLARE_METATYPE (CSMWorld::CellCoordinates) + +#endif diff --git a/apps/opencs/model/world/cellselection.cpp b/apps/opencs/model/world/cellselection.cpp new file mode 100644 index 000000000..73b5196f1 --- /dev/null +++ b/apps/opencs/model/world/cellselection.cpp @@ -0,0 +1,83 @@ + +#include "cellselection.hpp" + +#include +#include +#include + +CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::begin() const +{ + return mCells.begin(); +} + +CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::end() const +{ + return mCells.end(); +} + +bool CSMWorld::CellSelection::add (const CellCoordinates& coordinates) +{ + return mCells.insert (coordinates).second; +} + +void CSMWorld::CellSelection::remove (const CellCoordinates& coordinates) +{ + mCells.erase (coordinates); +} + +bool CSMWorld::CellSelection::has (const CellCoordinates& coordinates) const +{ + return mCells.find (coordinates)!=end(); +} + +int CSMWorld::CellSelection::getSize() const +{ + return mCells.size(); +} + +CSMWorld::CellCoordinates CSMWorld::CellSelection::getCentre() const +{ + if (mCells.empty()) + throw std::logic_error ("call of getCentre on empty cell selection"); + + double x = 0; + double y = 0; + + for (Iterator iter = begin(); iter!=end(); ++iter) + { + x += iter->getX(); + y += iter->getY(); + } + + x /= mCells.size(); + y /= mCells.size(); + + Iterator closest = begin(); + double distance = std::numeric_limits::max(); + + for (Iterator iter (begin()); iter!=end(); ++iter) + { + double deltaX = x - iter->getX(); + double deltaY = y - iter->getY(); + + double delta = std::sqrt (deltaX * deltaX + deltaY * deltaY); + + if (deltamove (x, y)); + + mCells.swap (moved); +} diff --git a/apps/opencs/model/world/cellselection.hpp b/apps/opencs/model/world/cellselection.hpp new file mode 100644 index 000000000..042416a33 --- /dev/null +++ b/apps/opencs/model/world/cellselection.hpp @@ -0,0 +1,57 @@ +#ifndef CSM_WOLRD_CELLSELECTION_H +#define CSM_WOLRD_CELLSELECTION_H + +#include + +#include + +#include "cellcoordinates.hpp" + +namespace CSMWorld +{ + /// \brief Selection of cells in a paged worldspace + /// + /// \note The CellSelection does not specify the worldspace it applies to. + class CellSelection + { + public: + + typedef std::set Container; + typedef Container::const_iterator Iterator; + + private: + + Container mCells; + + public: + + Iterator begin() const; + + Iterator end() const; + + bool add (const CellCoordinates& coordinates); + ///< Ignored if the cell specified by \a coordinates is already part of the selection. + /// + /// \return Was a cell added to the collection? + + void remove (const CellCoordinates& coordinates); + ///< ignored if the cell specified by \a coordinates is not part of the selection. + + bool has (const CellCoordinates& coordinates) const; + ///< \return Is the cell specified by \a coordinates part of the selection? + + int getSize() const; + ///< Return number of cells. + + CellCoordinates getCentre() const; + ///< Return the selected cell that is closest to the geometric centre of the selection. + /// + /// \attention This function must not be called on selections that are empty. + + void move (int x, int y); + }; +} + +Q_DECLARE_METATYPE (CSMWorld::CellSelection) + +#endif From 0d352cb8832863445b7ad464610f8351c2588323 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 7 Apr 2014 14:16:02 +0200 Subject: [PATCH 61/85] replaced rectangular cell selection with a CellSelection object --- .../view/render/pagedworldspacewidget.cpp | 21 ++++++------ .../view/render/pagedworldspacewidget.hpp | 9 +++--- apps/opencs/view/world/scenesubview.cpp | 32 +++++++++++++------ apps/opencs/view/world/scenesubview.hpp | 7 +++- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 96d44543e..ab02d63ee 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -4,36 +4,37 @@ #include CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget *parent) -: WorldspaceWidget (parent), mMin (std::make_pair (0, 0)), mMax (std::make_pair (-1, -1)) +: WorldspaceWidget (parent) {} void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) { if (!hint.empty()) { + CSMWorld::CellSelection selection; + if (hint[0]=='c') { char ignore1, ignore2, ignore3; - std::pair cellIndex; + int x, y; std::istringstream stream (hint.c_str()); - if (stream >> ignore1 >> ignore2 >> ignore3 >> cellIndex.first >> cellIndex.second) + if (stream >> ignore1 >> ignore2 >> ignore3 >> x >> y) { - setCellIndex (cellIndex, cellIndex); + selection.add (CSMWorld::CellCoordinates (x, y)); /// \todo adjust camera position } } /// \todo implement 'r' type hints + + setCellSelection (selection); } } -void CSVRender::PagedWorldspaceWidget::setCellIndex (const std::pair& min, - const std::pair& max) +void CSVRender::PagedWorldspaceWidget::setCellSelection (const CSMWorld::CellSelection& selection) { - mMin = min; - mMax = max; - - emit cellIndexChanged (mMin, mMax); + mSelection = selection; + emit cellSelectionChanged (mSelection); } \ No newline at end of file diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 9a4b79c3a..f6ff32dc1 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -1,6 +1,8 @@ #ifndef OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H #define OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H +#include "../../model/world/cellselection.hpp" + #include "worldspacewidget.hpp" namespace CSVRender @@ -9,8 +11,7 @@ namespace CSVRender { Q_OBJECT - std::pair mMin; - std::pair mMax; + CSMWorld::CellSelection mSelection; public: @@ -21,11 +22,11 @@ namespace CSVRender virtual void useViewHint (const std::string& hint); - void setCellIndex (const std::pair& min, const std::pair& max); + void setCellSelection (const CSMWorld::CellSelection& selection); signals: - void cellIndexChanged (const std::pair& min, const std::pair& max); + void cellSelectionChanged (const CSMWorld::CellSelection& selection); }; } diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 4ebaa9352..0bb11ce8c 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -9,6 +9,8 @@ #include "../../model/doc/document.hpp" +#include "../../model/world/cellselection.hpp" + #include "../filter/filterbox.hpp" #include "../render/pagedworldspacewidget.hpp" @@ -40,10 +42,8 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D { CSVRender::PagedWorldspaceWidget *widget = new CSVRender::PagedWorldspaceWidget (this); mScene = widget; - connect (widget, - SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), - this, - SLOT (cellIndexChanged (const std::pair&, const std::pair&))); + connect (widget, SIGNAL (cellSelectionChanged (const CSMWorld::CellSelection&)), + this, SLOT (cellSelectionChanged (const CSMWorld::CellSelection&))); } else mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); @@ -102,16 +102,28 @@ void CSVWorld::SceneSubView::closeRequest() deleteLater(); } -void CSVWorld::SceneSubView::cellIndexChanged (const std::pair& min, - const std::pair& max) +void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection& selection) { + int size = selection.getSize(); + std::ostringstream stream; - stream << "Scene: " << getUniversalId().getId() << " (" << min.first << ", " << min.second; + stream << "Scene: " << getUniversalId().getId(); - if (min!=max) - stream << " to " << max.first << ", " << max.second; + if (size==0) + stream << " (empty)"; + else if (size==1) + { + stream << " (" << *selection.begin() << ")"; + } + else + { + stream << " (" << selection.getCentre() << " and " << size-1 << " more "; - stream << ")"; + if (size>1) + stream << "cells around it)"; + else + stream << "cell around it)"; + } setWindowTitle (QString::fromUtf8 (stream.str().c_str())); } \ No newline at end of file diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index ee5b7b41f..0b15ea541 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -5,6 +5,11 @@ class QModelIndex; +namespace CSMWorld +{ + class CellSelection; +} + namespace CSMDoc { class Document; @@ -44,7 +49,7 @@ namespace CSVWorld void closeRequest(); - void cellIndexChanged (const std::pair& min, const std::pair& max); + void cellSelectionChanged (const CSMWorld::CellSelection& selection); }; } From e0550ba336e7d0bd0b884ceb3facbc51b61d51c9 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 7 Apr 2014 15:23:14 +0200 Subject: [PATCH 62/85] allow multiple cell coordinates in c-type hint for scene subviews --- .../view/render/pagedworldspacewidget.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index ab02d63ee..0f23dfe7b 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -15,19 +15,26 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) if (hint[0]=='c') { - char ignore1, ignore2, ignore3; - int x, y; + // syntax: c:#x1 y1; #x2 y2 (number of coordinate pairs can be 0 or larger) + char ignore; std::istringstream stream (hint.c_str()); - if (stream >> ignore1 >> ignore2 >> ignore3 >> x >> y) + if (stream >> ignore) { - selection.add (CSMWorld::CellCoordinates (x, y)); + char ignore1; // : or ; + char ignore2; // # + int x, y; + + while (stream >> ignore1 >> ignore2 >> x >> y) + selection.add (CSMWorld::CellCoordinates (x, y)); /// \todo adjust camera position } } - - /// \todo implement 'r' type hints + else if (hint[0]=='r') + { + /// \todo implement 'r' type hints + } setCellSelection (selection); } From 09bd0324c92b7c6125e291d1bbaf6cba601db2e3 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Tue, 8 Apr 2014 20:19:09 +0200 Subject: [PATCH 63/85] Fail properly when a content file is not found --- apps/openmw/mwworld/worldimp.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 594a9f7f4..f808856c5 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2071,6 +2071,12 @@ namespace MWWorld { contentLoader.load(col.getPath(*it), idx); } + else + { + std::stringstream msg; + msg << "Failed loading " << *it << ": the content file does not exist"; + throw std::runtime_error(msg.str()); + } } } From 0516d95253a1baecff5136f78a93124c1f778b90 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 10 Apr 2014 22:12:09 +0200 Subject: [PATCH 64/85] added context menu with selection functions to region map --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/regionmap.cpp | 11 ++ apps/opencs/model/world/regionmap.hpp | 5 + apps/opencs/view/world/regionmap.cpp | 142 ++++++++++++++++++++ apps/opencs/view/world/regionmap.hpp | 45 +++++++ apps/opencs/view/world/regionmapsubview.cpp | 19 +-- apps/opencs/view/world/regionmapsubview.hpp | 8 +- 7 files changed, 214 insertions(+), 18 deletions(-) create mode 100644 apps/opencs/view/world/regionmap.cpp create mode 100644 apps/opencs/view/world/regionmap.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d9e271498..cbe90b1d3 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool - scenetoolmode infocreator scriptedit dialoguesubview previewsubview + scenetoolmode infocreator scriptedit dialoguesubview previewsubview regionmap ) opencs_units (view/render diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 697c5f450..544d5cbc5 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -406,6 +406,17 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const return QString::fromUtf8 (stream.str().c_str()); } + if (role==Role_Region) + { + CellIndex cellIndex = getIndex (index); + + std::map::const_iterator cell = + mMap.find (cellIndex); + + if (cell!=mMap.end() && !cell->second.mRegion.empty()) + return QString::fromUtf8 (Misc::StringUtils::lowerCase (cell->second.mRegion).c_str()); + } + return QVariant(); } diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 7fb89f20a..699166a72 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -25,6 +25,11 @@ namespace CSMWorld typedef std::pair CellIndex; + enum Role + { + Role_Region = Qt::UserRole + }; + private: struct CellDescription diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp new file mode 100644 index 000000000..9502c423f --- /dev/null +++ b/apps/opencs/view/world/regionmap.cpp @@ -0,0 +1,142 @@ + +#include "regionmap.hpp" + +#include +#include + +#include +#include +#include + +#include "../../model/world/regionmap.hpp" + +void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) +{ + QMenu menu (this); + + if (getUnselectedCells().size()>0) + menu.addAction (mSelectAllAction); + + if (selectionModel()->selectedIndexes().size()>0) + menu.addAction (mClearSelectionAction); + + if (getMissingRegionCells().size()>0) + menu.addAction (mSelectRegionsAction); + + menu.exec (event->globalPos()); +} + +QModelIndexList CSVWorld::RegionMap::getUnselectedCells() const +{ + const QAbstractItemModel *model = QTableView::model(); + + int rows = model->rowCount(); + int columns = model->columnCount(); + + QModelIndexList selected = selectionModel()->selectedIndexes(); + std::sort (selected.begin(), selected.end()); + + QModelIndexList all; + + for (int y=0; yindex (y, x); + if (model->data (index, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern)) + all.push_back (index); + } + + std::sort (all.begin(), all.end()); + + QModelIndexList list; + + std::set_difference (all.begin(), all.end(), selected.begin(), selected.end(), + std::back_inserter (list)); + + return list; +} + +QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const +{ + const QAbstractItemModel *model = QTableView::model(); + + QModelIndexList selected = selectionModel()->selectedIndexes(); + + std::set regions; + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + std::string region = + model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData(); + + if (!region.empty()) + regions.insert (region); + } + + QModelIndexList list; + + QModelIndexList unselected = getUnselectedCells(); + + for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) + { + std::string region = + model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData(); + + if (!region.empty() && regions.find (region)!=regions.end()) + list.push_back (*iter); + } + + return list; +} + +CSVWorld::RegionMap::RegionMap (QAbstractItemModel *model, QWidget *parent) +: QTableView (parent) +{ + verticalHeader()->hide(); + horizontalHeader()->hide(); + + setSelectionMode (QAbstractItemView::ExtendedSelection); + + setModel (model); + + resizeColumnsToContents(); + resizeRowsToContents(); + + mSelectAllAction = new QAction (tr ("Select All"), this); + connect (mSelectAllAction, SIGNAL (triggered()), this, SLOT (selectAll())); + addAction (mSelectAllAction); + + mClearSelectionAction = new QAction (tr ("Clear Selection"), this); + connect (mClearSelectionAction, SIGNAL (triggered()), this, SLOT (clearSelection())); + addAction (mClearSelectionAction); + + mSelectRegionsAction = new QAction (tr ("Select Regions"), this); + connect (mSelectRegionsAction, SIGNAL (triggered()), this, SLOT (selectRegions())); + addAction (mSelectRegionsAction); +} + +void CSVWorld::RegionMap::setEditLock (bool locked) +{ + +} + +void CSVWorld::RegionMap::selectAll() +{ + QModelIndexList unselected = getUnselectedCells(); + + for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) + selectionModel()->select (*iter, QItemSelectionModel::Select); +} + +void CSVWorld::RegionMap::clearSelection() +{ + selectionModel()->clearSelection(); +} + +void CSVWorld::RegionMap::selectRegions() +{ + QModelIndexList unselected = getMissingRegionCells(); + + for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) + selectionModel()->select (*iter, QItemSelectionModel::Select); +} \ No newline at end of file diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp new file mode 100644 index 000000000..e30267b03 --- /dev/null +++ b/apps/opencs/view/world/regionmap.hpp @@ -0,0 +1,45 @@ +#ifndef CSV_WORLD_REGIONMAP_H +#define CSV_WORLD_REGIONMAP_H + +#include + +class QAction; +class QAbstractItemModel; + +namespace CSVWorld +{ + class RegionMap : public QTableView + { + Q_OBJECT + + QAction *mSelectAllAction; + QAction *mClearSelectionAction; + QAction *mSelectRegionsAction; + + private: + + void contextMenuEvent (QContextMenuEvent *event); + + QModelIndexList getUnselectedCells() const; + ///< Note non-existent cells are not listed. + + QModelIndexList getMissingRegionCells() const; + ///< Unselected cells within all regions that have at least one selected cell. + + public: + + RegionMap (QAbstractItemModel *model, QWidget *parent = 0); + + void setEditLock (bool locked); + + private slots: + + void selectAll(); + + void clearSelection(); + + void selectRegions(); + }; +} + +#endif diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp index b82c1afb5..e170ee309 100644 --- a/apps/opencs/view/world/regionmapsubview.cpp +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -1,29 +1,18 @@ #include "regionmapsubview.hpp" -#include -#include +#include "regionmap.hpp" CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document) : CSVDoc::SubView (universalId) { - mTable = new QTableView (this); + mRegionMap = new RegionMap (document.getData().getTableModel (universalId), this); - mTable->verticalHeader()->hide(); - mTable->horizontalHeader()->hide(); - - mTable->setSelectionMode (QAbstractItemView::ExtendedSelection); - - mTable->setModel (document.getData().getTableModel (universalId)); - - mTable->resizeColumnsToContents(); - mTable->resizeRowsToContents(); - - setWidget (mTable); + setWidget (mRegionMap); } void CSVWorld::RegionMapSubView::setEditLock (bool locked) { - + mRegionMap->setEditLock (locked); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmapsubview.hpp b/apps/opencs/view/world/regionmapsubview.hpp index 1655107af..f329c7f3b 100644 --- a/apps/opencs/view/world/regionmapsubview.hpp +++ b/apps/opencs/view/world/regionmapsubview.hpp @@ -3,7 +3,7 @@ #include "../doc/subview.hpp" -class QTableView; +class QAction; namespace CSMDoc { @@ -12,9 +12,13 @@ namespace CSMDoc namespace CSVWorld { + class RegionMap; + class RegionMapSubView : public CSVDoc::SubView { - QTableView *mTable; + Q_OBJECT + + RegionMap *mRegionMap; public: From d0ea23431c2417dd86df9aa650a0978ee8ca7749 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 11 Apr 2014 10:06:16 +0200 Subject: [PATCH 65/85] replaced CellIndex typedef with new CellCoordinates class --- apps/opencs/model/world/regionmap.cpp | 149 ++++++++++---------------- apps/opencs/model/world/regionmap.hpp | 19 ++-- 2 files changed, 67 insertions(+), 101 deletions(-) diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 544d5cbc5..7f233a433 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -1,6 +1,7 @@ #include "regionmap.hpp" +#include #include #include @@ -25,17 +26,16 @@ CSMWorld::RegionMap::CellDescription::CellDescription (const Record& cell) mName = cell2.mName; } -CSMWorld::RegionMap::CellIndex CSMWorld::RegionMap::getIndex (const QModelIndex& index) const +CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex (const QModelIndex& index) const { - return CellIndex (index.column()+mMin.first, - (mMax.second-mMin.second - index.row()-1)+mMin.second); + return mMin.move (index.column(), mMax.getY()-mMin.getY() - index.row()-1); } -QModelIndex CSMWorld::RegionMap::getIndex (const CellIndex& index) const +QModelIndex CSMWorld::RegionMap::getIndex (const CellCoordinates& index) const { // I hate you, Qt API naming scheme! - return QAbstractTableModel::index (mMax.second-mMin.second - (index.second-mMin.second)-1, - index.first-mMin.first); + return QAbstractTableModel::index (mMax.getY()-mMin.getY() - (index.getY()-mMin.getY())-1, + index.getX()-mMin.getX()); } void CSMWorld::RegionMap::buildRegions() @@ -70,21 +70,21 @@ void CSMWorld::RegionMap::buildMap() { CellDescription description (cell); - CellIndex index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index (cell2.mData.mX, cell2.mData.mY); mMap.insert (std::make_pair (index, description)); } } - std::pair mapSize = getSize(); + std::pair mapSize = getSize(); mMin = mapSize.first; mMax = mapSize.second; } -void CSMWorld::RegionMap::addCell (const CellIndex& index, const CellDescription& description) +void CSMWorld::RegionMap::addCell (const CellCoordinates& index, const CellDescription& description) { - std::map::iterator cell = mMap.find (index); + std::map::iterator cell = mMap.find (index); if (cell!=mMap.end()) { @@ -114,7 +114,7 @@ void CSMWorld::RegionMap::addCells (int start, int end) if (cell2.isExterior()) { - CellIndex index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index (cell2.mData.mX, cell2.mData.mY); CellDescription description (cell); @@ -123,9 +123,9 @@ void CSMWorld::RegionMap::addCells (int start, int end) } } -void CSMWorld::RegionMap::removeCell (const CellIndex& index) +void CSMWorld::RegionMap::removeCell (const CellCoordinates& index) { - std::map::iterator cell = mMap.find (index); + std::map::iterator cell = mMap.find (index); if (cell!=mMap.end()) { @@ -160,7 +160,7 @@ void CSMWorld::RegionMap::updateRegions (const std::vector& regions std::for_each (regions2.begin(), regions2.end(), &Misc::StringUtils::lowerCase); std::sort (regions2.begin(), regions2.end()); - for (std::map::const_iterator iter (mMap.begin()); + for (std::map::const_iterator iter (mMap.begin()); iter!=mMap.end(); ++iter) { if (!iter->second.mRegion.empty() && @@ -176,90 +176,57 @@ void CSMWorld::RegionMap::updateRegions (const std::vector& regions void CSMWorld::RegionMap::updateSize() { - std::pair size = getSize(); + std::pair size = getSize(); + if (int diff = size.first.getX() - mMin.getX()) { - int diff = size.first.first - mMin.first; - - if (diff<0) - { - beginInsertColumns (QModelIndex(), 0, -diff-1); - mMin.first = size.first.first; - endInsertColumns(); - } - else if (diff>0) - { - beginRemoveColumns (QModelIndex(), 0, diff-1); - mMin.first = size.first.first; - endRemoveColumns(); - } + beginInsertColumns (QModelIndex(), 0, std::abs (diff)-1); + mMin = CellCoordinates (size.first.getX(), mMin.getY()); + endInsertColumns(); } + if (int diff = size.first.getY() - mMin.getY()) { - int diff = size.first.second - mMin.second; - - if (diff<0) - { - beginInsertRows (QModelIndex(), 0, -diff-1); - mMin.second = size.first.second; - endInsertRows(); - } - else if (diff>0) - { - beginRemoveRows (QModelIndex(), 0, diff-1); - mMin.second = size.first.second; - endRemoveRows(); - } + beginInsertRows (QModelIndex(), 0, std::abs (diff)-1); + mMin = CellCoordinates (mMin.getX(), size.first.getY()); + endInsertRows(); } + if (int diff = size.second.getX() - mMax.getX()) { - int diff = size.second.first - mMax.first; + int columns = columnCount(); if (diff>0) - { - int columns = columnCount(); beginInsertColumns (QModelIndex(), columns, columns+diff-1); - mMax.first = size.second.first; - endInsertColumns(); - } - else if (diff<0) - { - int columns = columnCount(); + else beginRemoveColumns (QModelIndex(), columns+diff, columns-1); - mMax.first = size.second.first; - endRemoveColumns(); - } + + mMax = CellCoordinates (size.second.getX(), mMax.getY()); + endInsertColumns(); } + if (int diff = size.second.getY() - mMax.getY()) { - int diff = size.second.second - mMax.second; + int rows = rowCount(); if (diff>0) - { - int rows = rowCount(); beginInsertRows (QModelIndex(), rows, rows+diff-1); - mMax.second = size.second.second; - endInsertRows(); - } - else if (diff<0) - { - int rows = rowCount(); + else beginRemoveRows (QModelIndex(), rows+diff, rows-1); - mMax.second = size.second.second; - endRemoveRows(); - } + + mMax = CellCoordinates (mMax.getX(), size.second.getY()); + endInsertRows(); } } -std::pair CSMWorld::RegionMap::getSize() - const +std::pair CSMWorld::RegionMap::getSize() const { const IdCollection& cells = mData.getCells(); int size = cells.getSize(); - CellIndex min (0, 0); - CellIndex max (0, 0); + CellCoordinates min (0, 0); + CellCoordinates max (0, 0); for (int i=0; i CSMWor if (cell2.isExterior()) { - CellIndex index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index (cell2.mData.mX, cell2.mData.mY); if (min==max) { min = index; - max = std::make_pair (min.first+1, min.second+1); + max = min.move (1, 1); } else { - if (index.first=max.first) - max.first = index.first + 1; - - if (index.second=max.second) - max.second = index.second + 1; + if (index.getX()=max.getX()) + max = CellCoordinates (index.getX()+1, max.getY()); + + if (index.getY()=max.getY()) + max = CellCoordinates (max.getX(), index.getY() + 1); } } } @@ -323,7 +290,7 @@ int CSMWorld::RegionMap::rowCount (const QModelIndex& parent) const if (parent.isValid()) return 0; - return mMax.second-mMin.second; + return mMax.getY()-mMin.getY(); } int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const @@ -331,7 +298,7 @@ int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const if (parent.isValid()) return 0; - return mMax.first-mMin.first; + return mMax.getX()-mMin.getX(); } QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const @@ -343,7 +310,7 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const { /// \todo GUI class in non-GUI code. Needs to be addressed eventually. - std::map::const_iterator cell = + std::map::const_iterator cell = mMap.find (getIndex (index)); if (cell!=mMap.end()) @@ -370,13 +337,13 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const if (role==Qt::ToolTipRole) { - CellIndex cellIndex = getIndex (index); + CellCoordinates cellIndex = getIndex (index); std::ostringstream stream; - stream << cellIndex.first << ", " << cellIndex.second; + stream << cellIndex; - std::map::const_iterator cell = + std::map::const_iterator cell = mMap.find (cellIndex); if (cell!=mMap.end()) @@ -408,9 +375,9 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const if (role==Role_Region) { - CellIndex cellIndex = getIndex (index); + CellCoordinates cellIndex = getIndex (index); - std::map::const_iterator cell = + std::map::const_iterator cell = mMap.find (cellIndex); if (cell!=mMap.end() && !cell->second.mRegion.empty()) @@ -503,7 +470,7 @@ void CSMWorld::RegionMap::cellsAboutToBeRemoved (const QModelIndex& parent, int if (cell2.isExterior()) { - CellIndex index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index (cell2.mData.mX, cell2.mData.mY); removeCell (index); } diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 699166a72..29cc8c512 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -9,6 +9,7 @@ #include "record.hpp" #include "cell.hpp" +#include "cellcoordinates.hpp" namespace CSMWorld { @@ -23,8 +24,6 @@ namespace CSMWorld public: - typedef std::pair CellIndex; - enum Role { Role_Region = Qt::UserRole @@ -44,27 +43,27 @@ namespace CSMWorld }; Data& mData; - std::map mMap; - CellIndex mMin; ///< inclusive - CellIndex mMax; ///< exclusive + std::map mMap; + CellCoordinates mMin; ///< inclusive + CellCoordinates mMax; ///< exclusive std::map mColours; ///< region ID, colour (RGBA) - CellIndex getIndex (const QModelIndex& index) const; + CellCoordinates getIndex (const QModelIndex& index) const; ///< Translates a Qt model index into a cell index (which can contain negative components) - QModelIndex getIndex (const CellIndex& index) const; + QModelIndex getIndex (const CellCoordinates& index) const; void buildRegions(); void buildMap(); - void addCell (const CellIndex& index, const CellDescription& description); + void addCell (const CellCoordinates& index, const CellDescription& description); ///< May be called on a cell that is already in the map (in which case an update is // performed) void addCells (int start, int end); - void removeCell (const CellIndex& index); + void removeCell (const CellCoordinates& index); ///< May be called on a cell that is not in the map (in which case the call is ignored) void addRegion (const std::string& region, unsigned int colour); @@ -83,7 +82,7 @@ namespace CSMWorld void updateSize(); - std::pair getSize() const; + std::pair getSize() const; public: From d2a41167d03a46808ab71542d0ab8c0d5c4388d9 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 11 Apr 2014 21:24:00 +1000 Subject: [PATCH 66/85] Allow flying and swimming creatures to step inclines. Should have listen to Chris in the first place, see https://forum.openmw.org/viewtopic.php?f=6&t=2075 --- apps/openmw/mwworld/physicssystem.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index c2d902d69..7d531d6d3 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -308,11 +308,17 @@ namespace MWWorld break; } + Ogre::Vector3 oldPosition = newPosition; // We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over) - // NOTE: May need to stop slaughterfish step out of the water. - // NOTE: stepMove may modify newPosition - if((canWalk || isBipedal || isNpc) && stepMove(colobj, newPosition, velocity, remainingTime, engine)) - isOnGround = !(newPosition.z < waterlevel || isFlying); // Only on the ground if there's gravity + // NOTE: stepMove modifies newPosition if successful + if(stepMove(colobj, newPosition, velocity, remainingTime, engine)) + { + // don't let slaughterfish move out of water after stepMove + if(ptr.getClass().canSwim(ptr) && newPosition.z > (waterlevel - halfExtents.z * 0.5)) + newPosition = oldPosition; + else // Only on the ground if there's gravity + isOnGround = !(newPosition.z < waterlevel || isFlying); + } else { // Can't move this way, try to find another spot along the plane From 2f63eb7ca46f869fc4493488db1f9b465665970d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 12 Apr 2014 20:07:09 +0200 Subject: [PATCH 67/85] added missing edit lock for record reordering --- apps/opencs/view/world/table.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index a2927c2f0..712b8f556 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -358,6 +358,9 @@ void CSVWorld::Table::cloneRecord() void CSVWorld::Table::moveUpRecord() { + if (mEditLock) + return; + QModelIndexList selectedRows = selectionModel()->selectedRows(); if (selectedRows.size()==1) @@ -387,6 +390,9 @@ void CSVWorld::Table::moveUpRecord() void CSVWorld::Table::moveDownRecord() { + if (mEditLock) + return; + QModelIndexList selectedRows = selectionModel()->selectedRows(); if (selectedRows.size()==1) From 0bef75487366b786c3db147181ea009f8649b86b Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 13 Apr 2014 08:46:02 +1000 Subject: [PATCH 68/85] Fix jumping animation glitches caused by minor vertical movements. Should resolve Bug #1271. --- apps/openmw/mwmechanics/character.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 93c789af1..b7d99adec 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1090,7 +1090,13 @@ void CharacterController::update(float duration) if (inwater || flying) cls.getCreatureStats(mPtr).land(); - if(!onground && !flying && !inwater) + // FIXME: The check for vec.z is a hack, but onground is not a reliable + // indicator of whether the actor is on the ground (defaults to false, which + // means this code block will always execute at least once for most, and + // collisions can move z position slightly off zero). A very small value of + // 0.1 is used here, but maybe something larger like 10 should be used. + // Should resolve Bug#1271. + if(!onground && !flying && !inwater && vec.z > 0.1f) { // In the air (either getting up —ascending part of jump— or falling). From e9be6d3f42fa89ef52ca3393e46db66d833e9b0d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 13 Apr 2014 11:34:59 +1000 Subject: [PATCH 69/85] Fix falling animation where vec.z is set to zero. --- apps/openmw/mwmechanics/character.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b7d99adec..d34812417 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1090,13 +1090,14 @@ void CharacterController::update(float duration) if (inwater || flying) cls.getCreatureStats(mPtr).land(); - // FIXME: The check for vec.z is a hack, but onground is not a reliable - // indicator of whether the actor is on the ground (defaults to false, which - // means this code block will always execute at least once for most, and - // collisions can move z position slightly off zero). A very small value of - // 0.1 is used here, but maybe something larger like 10 should be used. - // Should resolve Bug#1271. - if(!onground && !flying && !inwater && vec.z > 0.1f) + if(!onground && !flying && !inwater + // FIXME: The check for vec.z is a hack, but onground is not a reliable + // indicator of whether the actor is on the ground (defaults to false, which + // means this code block will always execute at least once for most actors, + // and collisions can move z position slightly off zero). A very small value + // of 0.1 is used here, but something larger like 10 may be more suitable. + // Should resolve Bug#1271. + && (mJumpState == JumpState_Falling || vec.z > 0.1f)) { // In the air (either getting up —ascending part of jump— or falling). From 3e6e325e5b44e199b84ffa417765480d717962bc Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 13 Apr 2014 14:53:36 +1000 Subject: [PATCH 70/85] Instead of hacking character.cpp, provide a more reliable check for world->isOnGround(mPtr). --- apps/openmw/mwmechanics/character.cpp | 9 +-------- apps/openmw/mwworld/worldimp.cpp | 29 ++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d34812417..93c789af1 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1090,14 +1090,7 @@ void CharacterController::update(float duration) if (inwater || flying) cls.getCreatureStats(mPtr).land(); - if(!onground && !flying && !inwater - // FIXME: The check for vec.z is a hack, but onground is not a reliable - // indicator of whether the actor is on the ground (defaults to false, which - // means this code block will always execute at least once for most actors, - // and collisions can move z position slightly off zero). A very small value - // of 0.1 is used here, but something larger like 10 may be more suitable. - // Should resolve Bug#1271. - && (mJumpState == JumpState_Falling || vec.z > 0.1f)) + if(!onground && !flying && !inwater) { // In the air (either getting up —ascending part of jump— or falling). diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f808856c5..83299e126 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -9,6 +9,7 @@ #include +#include #include #include @@ -1711,11 +1712,37 @@ namespace MWWorld return pos.z < cell->getWaterLevel(); } + // physactor->getOnGround() is not a reliable indicator of whether the actor + // is on the ground (defaults to false, which means code blocks such as + // CharacterController::update() may falsely detect "falling"). + // + // Also, collisions can move z position slightly off zero, giving a false + // indication. In order to reduce false detection of jumping, small distance + // below the actor is detected and ignored. A value of 1.5 is used here, but + // something larger may be more suitable. This change should resolve Bug#1271. + // + // FIXME: Collision detection each time may have a performance impact. + // There might be better places to update PhysicActor::mOnGround. bool World::isOnGround(const MWWorld::Ptr &ptr) const { RefData &refdata = ptr.getRefData(); const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); - return physactor && physactor->getOnGround(); + if(physactor) + { + Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + OEngine::Physic::ActorTracer tracer; + // a small distance above collision object is considered "on ground" + tracer.findGround(physactor->getCollisionBody(), + pos, + pos - Ogre::Vector3(0, 0, 1.5f), // trace a small amount down + mPhysEngine); + if(tracer.mFraction < 1.0f) // collision, must be close to something below + return true; // TODO: should update physactor + else + return physactor->getOnGround(); + } + else + return false; } bool World::vanityRotateCamera(float * rot) From 966ed468701799429663edc2d7bb1abc3a4618ae Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 13 Apr 2014 18:34:08 +1000 Subject: [PATCH 71/85] Better performance but less tolerant of collision induced glitches. Also had to use const_cast to cache on ground status. --- apps/openmw/mwworld/worldimp.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 83299e126..d56ca1f16 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1721,13 +1721,18 @@ namespace MWWorld // below the actor is detected and ignored. A value of 1.5 is used here, but // something larger may be more suitable. This change should resolve Bug#1271. // - // FIXME: Collision detection each time may have a performance impact. - // There might be better places to update PhysicActor::mOnGround. + // TODO: There might be better places to update PhysicActor::mOnGround. bool World::isOnGround(const MWWorld::Ptr &ptr) const { RefData &refdata = ptr.getRefData(); const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); - if(physactor) + + if(!physactor) + return false; + + if(physactor->getOnGround()) + return true; + else { Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); OEngine::Physic::ActorTracer tracer; @@ -1737,12 +1742,13 @@ namespace MWWorld pos - Ogre::Vector3(0, 0, 1.5f), // trace a small amount down mPhysEngine); if(tracer.mFraction < 1.0f) // collision, must be close to something below - return true; // TODO: should update physactor + { + const_cast (physactor)->setOnGround(true); + return true; + } else - return physactor->getOnGround(); + return false; } - else - return false; } bool World::vanityRotateCamera(float * rot) From 300eb6f444f75900a30937f029cf2a046909f2cf Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 13:23:50 +0200 Subject: [PATCH 72/85] make FNAM sub-record optional for all record types --- components/esm/loadacti.cpp | 4 ++-- components/esm/loadarmo.cpp | 4 ++-- components/esm/loadbody.cpp | 4 ++-- components/esm/loadbsgn.cpp | 4 ++-- components/esm/loadclas.cpp | 4 ++-- components/esm/loadfact.cpp | 4 ++-- components/esm/loadingr.cpp | 4 ++-- components/esm/loadlock.cpp | 4 ++-- components/esm/loadprob.cpp | 4 ++-- components/esm/loadrace.cpp | 4 ++-- components/esm/loadregn.cpp | 4 ++-- components/esm/loadrepa.cpp | 4 ++-- components/esm/loadsoun.cpp | 4 ++-- 13 files changed, 26 insertions(+), 26 deletions(-) diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index 6ba0df0b3..8efea3302 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -11,13 +11,13 @@ namespace ESM void Activator::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); mScript = esm.getHNOString("SCRI"); } void Activator::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); } diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index ec8ff4f20..5bf38c840 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -34,7 +34,7 @@ unsigned int Armor::sRecordId = REC_ARMO; void Armor::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); mScript = esm.getHNOString("SCRI"); esm.getHNT(mData, "AODT", 24); mIcon = esm.getHNOString("ITEX"); @@ -45,7 +45,7 @@ void Armor::load(ESMReader &esm) void Armor::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); esm.writeHNT("AODT", mData, 24); esm.writeHNOCString("ITEX", mIcon); diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index 4015e6c91..c45f8d252 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -12,13 +12,13 @@ namespace ESM void BodyPart::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mRace = esm.getHNString("FNAM"); + mRace = esm.getHNOString("FNAM"); esm.getHNT(mData, "BYDT", 4); } void BodyPart::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mRace); + esm.writeHNOCString("FNAM", mRace); esm.writeHNT("BYDT", mData, 4); } diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 55e1e7f65..db1a72a36 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -10,7 +10,7 @@ namespace ESM void BirthSign::load(ESMReader &esm) { - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); mTexture = esm.getHNOString("TNAM"); mDescription = esm.getHNOString("DESC"); @@ -19,7 +19,7 @@ void BirthSign::load(ESMReader &esm) void BirthSign::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("TNAM", mTexture); esm.writeHNOCString("DESC", mDescription); diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index 33489eec4..ec339bd15 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -41,7 +41,7 @@ const char *Class::sGmstSpecializationIds[3] = { void Class::load(ESMReader &esm) { - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "CLDT", 60); if (mData.mIsPlayable > 1) @@ -51,7 +51,7 @@ void Class::load(ESMReader &esm) } void Class::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("CLDT", mData, 60); esm.writeHNOString("DESC", mDescription); } diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 61fa90263..84be21938 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -28,7 +28,7 @@ namespace ESM void Faction::load(ESMReader &esm) { - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); // Read rank names. These are optional. int i = 0; @@ -52,7 +52,7 @@ void Faction::load(ESMReader &esm) } void Faction::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); for (int i = 0; i < 10; i++) { diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 0e0243362..5c98cb8b9 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -11,7 +11,7 @@ namespace ESM void Ingredient::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "IRDT", 56); mScript = esm.getHNOString("SCRI"); mIcon = esm.getHNOString("ITEX"); @@ -42,7 +42,7 @@ void Ingredient::load(ESMReader &esm) void Ingredient::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("IRDT", mData, 56); esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 9ffce78a7..42677a22b 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -11,7 +11,7 @@ namespace ESM void Lockpick::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "LKDT", 16); @@ -22,7 +22,7 @@ void Lockpick::load(ESMReader &esm) void Lockpick::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("LKDT", mData, 16); esm.writeHNOString("SCRI", mScript); diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index caa3d7e0e..b736bb64b 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -11,7 +11,7 @@ namespace ESM void Probe::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "PBDT", 16); @@ -22,7 +22,7 @@ void Probe::load(ESMReader &esm) void Probe::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("PBDT", mData, 16); esm.writeHNOString("SCRI", mScript); diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index e50e43a74..17f2e0267 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -20,14 +20,14 @@ namespace ESM void Race::load(ESMReader &esm) { - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "RADT", 140); mPowers.load(esm); mDescription = esm.getHNOString("DESC"); } void Race::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("RADT", mData, 140); mPowers.save(esm); esm.writeHNOString("DESC", mDescription); diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index fa4271e26..da03e009f 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -10,7 +10,7 @@ namespace ESM void Region::load(ESMReader &esm) { - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); if (esm.getVer() == VER_12) esm.getHNExact(&mData, sizeof(mData) - 2, "WEAT"); @@ -32,7 +32,7 @@ void Region::load(ESMReader &esm) } void Region::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); if (esm.getVersion() == VER_12) esm.writeHNT("WEAT", mData, sizeof(mData) - 2); diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index a7132828d..4e6cd7794 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -11,7 +11,7 @@ namespace ESM void Repair::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "RIDT", 16); @@ -22,7 +22,7 @@ void Repair::load(ESMReader &esm) void Repair::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("RIDT", mData, 16); esm.writeHNOString("SCRI", mScript); diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 49c9eb54e..28e4d7d9c 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -10,7 +10,7 @@ namespace ESM void Sound::load(ESMReader &esm) { - mSound = esm.getHNString("FNAM"); + mSound = esm.getHNOString("FNAM"); esm.getHNT(mData, "DATA", 3); /* cout << "vol=" << (int)data.volume @@ -21,7 +21,7 @@ void Sound::load(ESMReader &esm) } void Sound::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mSound); + esm.writeHNOCString("FNAM", mSound); esm.writeHNT("DATA", mData, 3); } From decd4270d9aa1b31729f4aa0c195d7b04c768f14 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 13:59:27 +0200 Subject: [PATCH 73/85] added CellId role to RegionMap model --- apps/opencs/model/world/regionmap.cpp | 15 +++++++++++---- apps/opencs/model/world/regionmap.hpp | 5 ++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 7f233a433..fc4638432 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -384,15 +384,22 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const return QString::fromUtf8 (Misc::StringUtils::lowerCase (cell->second.mRegion).c_str()); } + if (role==Role_CellId) + { + CellCoordinates cellIndex = getIndex (index); + + std::ostringstream stream; + stream << "#" << cellIndex.getX() << " " << cellIndex.getY(); + + return QString::fromUtf8 (stream.str().c_str()); + } + return QVariant(); } Qt::ItemFlags CSMWorld::RegionMap::flags (const QModelIndex& index) const { - if (mMap.find (getIndex (index))!=mMap.end()) - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; - - return 0; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } void CSMWorld::RegionMap::regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end) diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 29cc8c512..5b82ac217 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -26,7 +26,8 @@ namespace CSMWorld enum Role { - Role_Region = Qt::UserRole + Role_Region = Qt::UserRole, + Role_CellId = Qt::UserRole+1 }; private: @@ -93,6 +94,8 @@ namespace CSMWorld virtual int columnCount (const QModelIndex& parent = QModelIndex()) const; virtual QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const; + ///< \note Calling this function with role==Role_CellId may return the ID of a cell + /// that does not exist. virtual Qt::ItemFlags flags (const QModelIndex& index) const; From 19b31c4146efb4b5a0cbbb2d4d1714ab86d50ac2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 14:16:59 +0200 Subject: [PATCH 74/85] always use the cell ID instead of the exterior coordinate fields from the original cell struct --- apps/opencs/model/world/cell.hpp | 3 +++ apps/opencs/model/world/regionmap.cpp | 24 ++++++++++++++++-------- apps/opencs/model/world/regionmap.hpp | 2 ++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/cell.hpp b/apps/opencs/model/world/cell.hpp index 89854312a..e6f3c8c35 100644 --- a/apps/opencs/model/world/cell.hpp +++ b/apps/opencs/model/world/cell.hpp @@ -9,6 +9,9 @@ namespace CSMWorld { /// \brief Wrapper for Cell record + /// + /// \attention The mData.mX and mData.mY fields of the ESM::Cell struct are not used. + /// Exterior cell coordinates are encoded in the cell ID. struct Cell : public ESM::Cell { std::string mId; diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index fc4638432..5f030bb52 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -38,6 +38,18 @@ QModelIndex CSMWorld::RegionMap::getIndex (const CellCoordinates& index) const index.getX()-mMin.getX()); } +CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex (const Cell& cell) const +{ + std::istringstream stream (cell.mId); + + char ignore; + int x = 0; + int y = 0; + stream >> ignore >> x >> y; + + return CellCoordinates (x, y); +} + void CSMWorld::RegionMap::buildRegions() { const IdCollection& regions = mData.getRegions(); @@ -70,7 +82,7 @@ void CSMWorld::RegionMap::buildMap() { CellDescription description (cell); - CellCoordinates index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index = getIndex (cell2); mMap.insert (std::make_pair (index, description)); } @@ -114,7 +126,7 @@ void CSMWorld::RegionMap::addCells (int start, int end) if (cell2.isExterior()) { - CellCoordinates index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index = getIndex (cell2); CellDescription description (cell); @@ -236,7 +248,7 @@ std::pair CSMWorld::Region if (cell2.isExterior()) { - CellCoordinates index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index = getIndex (cell2); if (min==max) { @@ -476,11 +488,7 @@ void CSMWorld::RegionMap::cellsAboutToBeRemoved (const QModelIndex& parent, int const Cell& cell2 = cell.get(); if (cell2.isExterior()) - { - CellCoordinates index (cell2.mData.mX, cell2.mData.mY); - - removeCell (index); - } + removeCell (getIndex (cell2)); } } diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 5b82ac217..7d7685e89 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -54,6 +54,8 @@ namespace CSMWorld QModelIndex getIndex (const CellCoordinates& index) const; + CellCoordinates getIndex (const Cell& cell) const; + void buildRegions(); void buildMap(); From fc4195a88fc682cb47b893523635aa4efc4b4347 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 14:17:18 +0200 Subject: [PATCH 75/85] added create cell menu item to regionmap --- apps/opencs/view/world/regionmap.cpp | 84 +++++++++++++++++++-- apps/opencs/view/world/regionmap.hpp | 25 +++++- apps/opencs/view/world/regionmapsubview.cpp | 2 +- 3 files changed, 102 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 9502c423f..1e44ff056 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -3,12 +3,19 @@ #include #include +#include #include #include #include +#include "../../model/doc/document.hpp" + #include "../../model/world/regionmap.hpp" +#include "../../model/world/universalid.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/commands.hpp" void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) { @@ -23,12 +30,28 @@ void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) if (getMissingRegionCells().size()>0) menu.addAction (mSelectRegionsAction); + int selectedNonExistentCells = getSelectedCells (false, true).size(); + + if (selectedNonExistentCells>0) + { + if (selectedNonExistentCells==1) + mCreateCellsAction->setText ("Create one cell"); + else + { + std::ostringstream stream; + stream << "Create " << selectedNonExistentCells << " cells"; + mCreateCellsAction->setText (QString::fromUtf8 (stream.str().c_str())); + } + + menu.addAction (mCreateCellsAction); + } + menu.exec (event->globalPos()); } QModelIndexList CSVWorld::RegionMap::getUnselectedCells() const { - const QAbstractItemModel *model = QTableView::model(); + const QAbstractItemModel *model = QTableView::model(); int rows = model->rowCount(); int columns = model->columnCount(); @@ -56,6 +79,25 @@ QModelIndexList CSVWorld::RegionMap::getUnselectedCells() const return list; } +QModelIndexList CSVWorld::RegionMap::getSelectedCells (bool existent, bool nonExistent) const +{ + const QAbstractItemModel *model = QTableView::model(); + + QModelIndexList selected = selectionModel()->selectedIndexes(); + + QModelIndexList list; + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + bool exists = model->data (*iter, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern); + + if ((exists && existent) || (!exists && nonExistent)) + list.push_back (*iter); + } + + return list; +} + QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const { const QAbstractItemModel *model = QTableView::model(); @@ -89,15 +131,16 @@ QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const return list; } -CSVWorld::RegionMap::RegionMap (QAbstractItemModel *model, QWidget *parent) -: QTableView (parent) +CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, + CSMDoc::Document& document, QWidget *parent) +: QTableView (parent), mEditLock (false), mDocument (document) { verticalHeader()->hide(); horizontalHeader()->hide(); setSelectionMode (QAbstractItemView::ExtendedSelection); - setModel (model); + setModel (document.getData().getTableModel (universalId)); resizeColumnsToContents(); resizeRowsToContents(); @@ -113,11 +156,15 @@ CSVWorld::RegionMap::RegionMap (QAbstractItemModel *model, QWidget *parent) mSelectRegionsAction = new QAction (tr ("Select Regions"), this); connect (mSelectRegionsAction, SIGNAL (triggered()), this, SLOT (selectRegions())); addAction (mSelectRegionsAction); + + mCreateCellsAction = new QAction (tr ("Create Cells Action"), this); + connect (mCreateCellsAction, SIGNAL (triggered()), this, SLOT (createCells())); + addAction (mCreateCellsAction); } void CSVWorld::RegionMap::setEditLock (bool locked) { - + mEditLock = locked; } void CSVWorld::RegionMap::selectAll() @@ -139,4 +186,31 @@ void CSVWorld::RegionMap::selectRegions() for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) selectionModel()->select (*iter, QItemSelectionModel::Select); +} + +void CSVWorld::RegionMap::createCells() +{ + if (mEditLock) + return; + + QModelIndexList selected = getSelectedCells (false, true); + + QAbstractItemModel *regionModel = model(); + + CSMWorld::IdTable *cellsModel = &dynamic_cast (* + mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + if (selected.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Create cells")); + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + std::string cellId = regionModel->data (*iter, CSMWorld::RegionMap::Role_CellId). + toString().toUtf8().constData(); + + mDocument.getUndoStack().push (new CSMWorld::CreateCommand (*cellsModel, cellId)); + } + + if (selected.size()>1) + mDocument.getUndoStack().endMacro(); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index e30267b03..5570cc585 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -4,7 +4,16 @@ #include class QAction; -class QAbstractItemModel; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class UniversalId; +} namespace CSVWorld { @@ -15,20 +24,28 @@ namespace CSVWorld QAction *mSelectAllAction; QAction *mClearSelectionAction; QAction *mSelectRegionsAction; + QAction *mCreateCellsAction; + bool mEditLock; + CSMDoc::Document& mDocument; private: void contextMenuEvent (QContextMenuEvent *event); QModelIndexList getUnselectedCells() const; - ///< Note non-existent cells are not listed. + ///< \note Non-existent cells are not listed. + + QModelIndexList getSelectedCells (bool existent = true, bool nonExistent = false) const; + ///< \param existant Include existant cells. + /// \param nonExistant Include non-existant cells. QModelIndexList getMissingRegionCells() const; ///< Unselected cells within all regions that have at least one selected cell. public: - RegionMap (QAbstractItemModel *model, QWidget *parent = 0); + RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, + QWidget *parent = 0); void setEditLock (bool locked); @@ -39,6 +56,8 @@ namespace CSVWorld void clearSelection(); void selectRegions(); + + void createCells(); }; } diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp index e170ee309..a966c419f 100644 --- a/apps/opencs/view/world/regionmapsubview.cpp +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -7,7 +7,7 @@ CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document) : CSVDoc::SubView (universalId) { - mRegionMap = new RegionMap (document.getData().getTableModel (universalId), this); + mRegionMap = new RegionMap (universalId, document, this); setWidget (mRegionMap); } From 1892550833e23bb064dfbeb679a74f5947be094c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 15:32:49 +0200 Subject: [PATCH 76/85] added set/unset region actions to region map --- apps/opencs/view/world/regionmap.cpp | 66 ++++++++++++++++++++++++++++ apps/opencs/view/world/regionmap.hpp | 10 +++++ 2 files changed, 76 insertions(+) diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 1e44ff056..7e0a30242 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -16,6 +16,7 @@ #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/commands.hpp" +#include "../../model/world/columns.hpp" void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) { @@ -46,6 +47,17 @@ void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) menu.addAction (mCreateCellsAction); } + if (getSelectedCells().size()>0) + { + if (!mRegionId.empty()) + { + mSetRegionAction->setText (QString::fromUtf8 (("Set region to " + mRegionId).c_str())); + menu.addAction (mSetRegionAction); + } + + menu.addAction (mUnsetRegionAction); + } + menu.exec (event->globalPos()); } @@ -131,6 +143,36 @@ QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const return list; } +void CSVWorld::RegionMap::setRegion (const std::string& regionId) +{ + QModelIndexList selected = getSelectedCells(); + + QAbstractItemModel *regionModel = model(); + + CSMWorld::IdTable *cellsModel = &dynamic_cast (* + mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + QString regionId2 = QString::fromUtf8 (regionId.c_str()); + + if (selected.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Set Region")); + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + std::string cellId = regionModel->data (*iter, CSMWorld::RegionMap::Role_CellId). + toString().toUtf8().constData(); + + QModelIndex index = cellsModel->getModelIndex (cellId, + cellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Region)); + + mDocument.getUndoStack().push ( + new CSMWorld::ModifyCommand (*cellsModel, index, regionId2)); + } + + if (selected.size()>1) + mDocument.getUndoStack().endMacro(); +} + CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, QWidget *parent) : QTableView (parent), mEditLock (false), mDocument (document) @@ -160,6 +202,14 @@ CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, mCreateCellsAction = new QAction (tr ("Create Cells Action"), this); connect (mCreateCellsAction, SIGNAL (triggered()), this, SLOT (createCells())); addAction (mCreateCellsAction); + + mSetRegionAction = new QAction (tr ("Set Region"), this); + connect (mSetRegionAction, SIGNAL (triggered()), this, SLOT (setRegion())); + addAction (mSetRegionAction); + + mUnsetRegionAction = new QAction (tr ("Unset Region"), this); + connect (mUnsetRegionAction, SIGNAL (triggered()), this, SLOT (unsetRegion())); + addAction (mUnsetRegionAction); } void CSVWorld::RegionMap::setEditLock (bool locked) @@ -213,4 +263,20 @@ void CSVWorld::RegionMap::createCells() if (selected.size()>1) mDocument.getUndoStack().endMacro(); +} + +void CSVWorld::RegionMap::setRegion() +{ + if (mEditLock) + return; + + setRegion (mRegionId); +} + +void CSVWorld::RegionMap::unsetRegion() +{ + if (mEditLock) + return; + + setRegion (""); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index 5570cc585..b93a13eb8 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -25,8 +25,11 @@ namespace CSVWorld QAction *mClearSelectionAction; QAction *mSelectRegionsAction; QAction *mCreateCellsAction; + QAction *mSetRegionAction; + QAction *mUnsetRegionAction; bool mEditLock; CSMDoc::Document& mDocument; + std::string mRegionId; private: @@ -42,6 +45,9 @@ namespace CSVWorld QModelIndexList getMissingRegionCells() const; ///< Unselected cells within all regions that have at least one selected cell. + void setRegion (const std::string& regionId); + ///< Set region Id of selected cells. + public: RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, @@ -58,6 +64,10 @@ namespace CSVWorld void selectRegions(); void createCells(); + + void setRegion(); + + void unsetRegion(); }; } From 2eca9e72fd154774757e111968bc53df8ed19d9a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 15:46:31 +0200 Subject: [PATCH 77/85] added view action to region map --- apps/opencs/view/world/regionmap.cpp | 41 ++++++++++++++++++--- apps/opencs/view/world/regionmap.hpp | 7 ++++ apps/opencs/view/world/regionmapsubview.cpp | 9 +++++ apps/opencs/view/world/regionmapsubview.hpp | 4 ++ 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 7e0a30242..5b15cc9a8 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -36,7 +36,7 @@ void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) if (selectedNonExistentCells>0) { if (selectedNonExistentCells==1) - mCreateCellsAction->setText ("Create one cell"); + mCreateCellsAction->setText ("Create one Cell"); else { std::ostringstream stream; @@ -51,13 +51,16 @@ void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) { if (!mRegionId.empty()) { - mSetRegionAction->setText (QString::fromUtf8 (("Set region to " + mRegionId).c_str())); + mSetRegionAction->setText (QString::fromUtf8 (("Set Region to " + mRegionId).c_str())); menu.addAction (mSetRegionAction); } menu.addAction (mUnsetRegionAction); } + if (selectionModel()->selectedIndexes().size()>0) + menu.addAction (mViewAction); + menu.exec (event->globalPos()); } @@ -210,6 +213,10 @@ CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, mUnsetRegionAction = new QAction (tr ("Unset Region"), this); connect (mUnsetRegionAction, SIGNAL (triggered()), this, SLOT (unsetRegion())); addAction (mUnsetRegionAction); + + mViewAction = new QAction (tr ("View Cells"), this); + connect (mViewAction, SIGNAL (triggered()), this, SLOT (view())); + addAction (mViewAction); } void CSVWorld::RegionMap::setEditLock (bool locked) @@ -245,8 +252,6 @@ void CSVWorld::RegionMap::createCells() QModelIndexList selected = getSelectedCells (false, true); - QAbstractItemModel *regionModel = model(); - CSMWorld::IdTable *cellsModel = &dynamic_cast (* mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); @@ -255,7 +260,7 @@ void CSVWorld::RegionMap::createCells() for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { - std::string cellId = regionModel->data (*iter, CSMWorld::RegionMap::Role_CellId). + std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). toString().toUtf8().constData(); mDocument.getUndoStack().push (new CSMWorld::CreateCommand (*cellsModel, cellId)); @@ -279,4 +284,30 @@ void CSVWorld::RegionMap::unsetRegion() return; setRegion (""); +} + +void CSVWorld::RegionMap::view() +{ + std::ostringstream hint; + hint << "c:"; + + QModelIndexList selected = selectionModel()->selectedIndexes(); + + bool first = true; + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). + toString().toUtf8().constData(); + + if (first) + first = false; + else + hint << "; "; + + hint << cellId; + } + + emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, "sys::default"), + hint.str()); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index b93a13eb8..12951b459 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -27,6 +27,7 @@ namespace CSVWorld QAction *mCreateCellsAction; QAction *mSetRegionAction; QAction *mUnsetRegionAction; + QAction *mViewAction; bool mEditLock; CSMDoc::Document& mDocument; std::string mRegionId; @@ -55,6 +56,10 @@ namespace CSVWorld void setEditLock (bool locked); + signals: + + void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); + private slots: void selectAll(); @@ -68,6 +73,8 @@ namespace CSVWorld void setRegion(); void unsetRegion(); + + void view(); }; } diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp index a966c419f..a7675a4a6 100644 --- a/apps/opencs/view/world/regionmapsubview.cpp +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -10,9 +10,18 @@ CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId, mRegionMap = new RegionMap (universalId, document, this); setWidget (mRegionMap); + + connect (mRegionMap, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), + this, SLOT (editRequest (const CSMWorld::UniversalId&, const std::string&))); } void CSVWorld::RegionMapSubView::setEditLock (bool locked) { mRegionMap->setEditLock (locked); +} + +void CSVWorld::RegionMapSubView::editRequest (const CSMWorld::UniversalId& id, + const std::string& hint) +{ + focusId (id, hint); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmapsubview.hpp b/apps/opencs/view/world/regionmapsubview.hpp index f329c7f3b..524727901 100644 --- a/apps/opencs/view/world/regionmapsubview.hpp +++ b/apps/opencs/view/world/regionmapsubview.hpp @@ -25,6 +25,10 @@ namespace CSVWorld RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document); virtual void setEditLock (bool locked); + + private slots: + + void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); }; } From 097c063b8a28cb6483c153507bff63fbf00e5e2c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 16:40:16 +0200 Subject: [PATCH 78/85] added 'view in table' action to region map --- apps/opencs/view/world/regionmap.cpp | 33 ++++++++++++++++++++++++++++ apps/opencs/view/world/regionmap.hpp | 3 +++ 2 files changed, 36 insertions(+) diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 5b15cc9a8..738de89ae 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -56,6 +56,8 @@ void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) } menu.addAction (mUnsetRegionAction); + + menu.addAction (mViewInTableAction); } if (selectionModel()->selectedIndexes().size()>0) @@ -217,6 +219,10 @@ CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, mViewAction = new QAction (tr ("View Cells"), this); connect (mViewAction, SIGNAL (triggered()), this, SLOT (view())); addAction (mViewAction); + + mViewInTableAction = new QAction (tr ("View Cells in Table"), this); + connect (mViewInTableAction, SIGNAL (triggered()), this, SLOT (viewInTable())); + addAction (mViewInTableAction); } void CSVWorld::RegionMap::setEditLock (bool locked) @@ -310,4 +316,31 @@ void CSVWorld::RegionMap::view() emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, "sys::default"), hint.str()); +} + +void CSVWorld::RegionMap::viewInTable() +{ + std::ostringstream hint; + hint << "f:!or("; + + QModelIndexList selected = getSelectedCells(); + + bool first = true; + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). + toString().toUtf8().constData(); + + if (first) + first = false; + else + hint << ","; + + hint << "string(ID,\"" << cellId << "\")"; + } + + hint << ")"; + + emit editRequest (CSMWorld::UniversalId::Type_Cells, hint.str()); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index 12951b459..c3757fe45 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -28,6 +28,7 @@ namespace CSVWorld QAction *mSetRegionAction; QAction *mUnsetRegionAction; QAction *mViewAction; + QAction *mViewInTableAction; bool mEditLock; CSMDoc::Document& mDocument; std::string mRegionId; @@ -75,6 +76,8 @@ namespace CSVWorld void unsetRegion(); void view(); + + void viewInTable(); }; } From d188e68227d2b6bd430bc49d6da6b00bb3634dd5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 16:40:41 +0200 Subject: [PATCH 79/85] added f-type hint to TableSubView --- apps/opencs/view/filter/filterbox.cpp | 15 +++++++++----- apps/opencs/view/filter/filterbox.hpp | 8 ++++++- apps/opencs/view/filter/recordfilterbox.cpp | 15 +++++++++----- apps/opencs/view/filter/recordfilterbox.hpp | 6 ++++++ apps/opencs/view/world/tablesubview.cpp | 23 ++++++++++++++------- apps/opencs/view/world/tablesubview.hpp | 8 +++++++ 6 files changed, 57 insertions(+), 18 deletions(-) diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index a33288025..0089143e9 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -15,23 +15,28 @@ CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent) layout->setContentsMargins (0, 0, 0, 0); - RecordFilterBox *recordFilterBox = new RecordFilterBox (data, this); + mRecordFilterBox = new RecordFilterBox (data, this); - layout->addWidget (recordFilterBox); + layout->addWidget (mRecordFilterBox); setLayout (layout); - connect (recordFilterBox, + connect (mRecordFilterBox, SIGNAL (filterChanged (boost::shared_ptr)), this, SIGNAL (recordFilterChanged (boost::shared_ptr))); connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - recordFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); + mRecordFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); - connect(this, SIGNAL(useFilterRequest(const std::string&)), recordFilterBox, SIGNAL(useFilterRequest(const std::string&))); + connect(this, SIGNAL(useFilterRequest(const std::string&)), mRecordFilterBox, SIGNAL(useFilterRequest(const std::string&))); setAcceptDrops(true); } +void CSVFilter::FilterBox::setRecordFilter (const std::string& filter) +{ + mRecordFilterBox->setFilter (filter); +} + void CSVFilter::FilterBox::dropEvent (QDropEvent* event) { std::vector data = dynamic_cast (event->mimeData())->getData(); diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp index 5954035fc..a8aa31953 100644 --- a/apps/opencs/view/filter/filterbox.hpp +++ b/apps/opencs/view/filter/filterbox.hpp @@ -16,10 +16,14 @@ namespace CSMWorld namespace CSVFilter { + class RecordFilterBox; + class FilterBox : public QWidget { Q_OBJECT + RecordFilterBox *mRecordFilterBox; + void dragEnterEvent (QDragEnterEvent* event); void dropEvent (QDropEvent* event); @@ -30,11 +34,13 @@ namespace CSVFilter FilterBox (CSMWorld::Data& data, QWidget *parent = 0); + void setRecordFilter (const std::string& filter); + signals: void recordFilterChanged (boost::shared_ptr filter); void recordDropped (std::vector& types, Qt::DropAction action); - void createFilterRequest(std::vector > >& filterSource, + void createFilterRequest(std::vector > >& filterSource, Qt::DropAction action); void useFilterRequest(const std::string& idOfFilter); }; diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index 2a1a1407f..f15b1e17a 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -15,18 +15,23 @@ CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *pare layout->addWidget (new QLabel ("Record Filter", this)); - EditWidget *editWidget = new EditWidget (data, this); + mEdit = new EditWidget (data, this); - layout->addWidget (editWidget); + layout->addWidget (mEdit); setLayout (layout); connect ( - editWidget, SIGNAL (filterChanged (boost::shared_ptr)), + mEdit, SIGNAL (filterChanged (boost::shared_ptr)), this, SIGNAL (filterChanged (boost::shared_ptr))); connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - editWidget, SLOT(createFilterRequest(std::vector > >&, Qt::DropAction))); + mEdit, SLOT(createFilterRequest(std::vector > >&, Qt::DropAction))); - connect(this, SIGNAL(useFilterRequest(const std::string&)), editWidget, SLOT(useFilterRequest(const std::string&))); + connect(this, SIGNAL(useFilterRequest(const std::string&)), mEdit, SLOT(useFilterRequest(const std::string&))); } + +void CSVFilter::RecordFilterBox::setFilter (const std::string& filter) +{ + mEdit->setText (QString::fromUtf8 (filter.c_str())); +} \ No newline at end of file diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp index fa5c9c3c2..8e01310a3 100644 --- a/apps/opencs/view/filter/recordfilterbox.hpp +++ b/apps/opencs/view/filter/recordfilterbox.hpp @@ -17,14 +17,20 @@ namespace CSMWorld namespace CSVFilter { + class EditWidget; + class RecordFilterBox : public QWidget { Q_OBJECT + EditWidget *mEdit; + public: RecordFilterBox (CSMWorld::Data& data, QWidget *parent = 0); + void setFilter (const std::string& filter); + signals: void filterChanged (boost::shared_ptr filter); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 2d08d186e..7f7b1477e 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -26,9 +26,9 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D layout->insertWidget (0, mTable = new Table (id, mBottom->canCreateAndDelete(), sorting, document), 2); - CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); + mFilterBox = new CSVFilter::FilterBox (document.getData(), this); - layout->insertWidget (0, filterBox); + layout->insertWidget (0, mFilterBox); QWidget *widget = new QWidget; @@ -48,7 +48,7 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D mTable->selectionSizeUpdate(); mTable->viewport()->installEventFilter(this); mBottom->installEventFilter(this); - filterBox->installEventFilter(this); + mFilterBox->installEventFilter(this); if (mBottom->canCreateAndDelete()) { @@ -63,17 +63,17 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect (mBottom, SIGNAL (requestFocus (const std::string&)), mTable, SLOT (requestFocus (const std::string&))); - connect (filterBox, + connect (mFilterBox, SIGNAL (recordFilterChanged (boost::shared_ptr)), mTable, SLOT (recordFilterChanged (boost::shared_ptr))); - connect(filterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), + connect(mFilterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), this, SLOT(createFilterRequest(std::vector&, Qt::DropAction))); - connect(this, SIGNAL(useFilterRequest(const std::string&)), filterBox, SIGNAL(useFilterRequest(const std::string&))); + connect(this, SIGNAL(useFilterRequest(const std::string&)), mFilterBox, SIGNAL(useFilterRequest(const std::string&))); connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - filterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); + mFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); } void CSVWorld::TableSubView::setEditLock (bool locked) @@ -97,6 +97,15 @@ void CSVWorld::TableSubView::setStatusBar (bool show) mBottom->setStatusBar (show); } +void CSVWorld::TableSubView::useHint (const std::string& hint) +{ + if (hint.empty()) + return; + + if (hint[0]=='f' && hint.size()>=2) + mFilterBox->setRecordFilter (hint.substr (2)); +} + void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) { emit cloneRequest(toClone.getId(), toClone.getType()); diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 910fec325..3f82a7592 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -17,6 +17,11 @@ namespace CSMDoc class Document; } +namespace CSVFilter +{ + class FilterBox; +} + namespace CSVWorld { class Table; @@ -29,6 +34,7 @@ namespace CSVWorld Table *mTable; TableBottomBox *mBottom; + CSVFilter::FilterBox *mFilterBox; public: @@ -41,6 +47,8 @@ namespace CSVWorld virtual void setStatusBar (bool show); + virtual void useHint (const std::string& hint); + protected: bool eventFilter(QObject* object, QEvent *event); From 89be1069a78e858f431112d6b0bc8423a6da6aa3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 14 Apr 2014 18:31:46 +1000 Subject: [PATCH 80/85] Bug #1260: show thief.dds image for a custom class level up menu --- apps/openmw/mwgui/levelupdialog.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 2f11a4d12..cd72f5677 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -131,7 +131,14 @@ namespace MWGui const ESM::Class *cls = world->getStore().get().find(playerData->mClass); - mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); + // Vanilla uses thief.dds for custom classes. A player with a custom class + // doesn't have mId set, see mwworld/esmstore.hpp where it is initialised as + // "$dynamic0". This check should resolve bug #1260. + if(world->getStore().get().isDynamic(cls->mId)) + mClassImage->setImageTexture ("textures\\levelup\\thief.dds"); + else + mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); + int level = creatureStats.getLevel ()+1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast(level)); From e2fab228f9956fb68e850dfbbe6d579d174184d3 Mon Sep 17 00:00:00 2001 From: Jeffrey Haines Date: Mon, 14 Apr 2014 18:11:04 -0400 Subject: [PATCH 81/85] Save state is handled correctly now. --- apps/openmw/mwmechanics/npcstats.cpp | 3 +++ apps/openmw/mwworld/player.cpp | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 4f014102d..e11e3b0c4 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -452,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()); @@ -504,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/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index e8179b9f3..a4a4e9568 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -196,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; @@ -241,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]; From 1fc030653fb9c3b25dc96ee5ac202a5eb5778d4c Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 15 Apr 2014 22:30:41 +1000 Subject: [PATCH 82/85] Avoid hard coding "thief.dds" string. --- apps/openmw/mwgui/levelupdialog.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index cd72f5677..5a3fc2855 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -134,12 +134,20 @@ namespace MWGui // Vanilla uses thief.dds for custom classes. A player with a custom class // doesn't have mId set, see mwworld/esmstore.hpp where it is initialised as // "$dynamic0". This check should resolve bug #1260. + // Choosing Stealth specialization and Speed/Agility as attributes. if(world->getStore().get().isDynamic(cls->mId)) - mClassImage->setImageTexture ("textures\\levelup\\thief.dds"); + { + MWWorld::SharedIterator it = world->getStore().get().begin(); + for(; it != world->getStore().get().end(); it++) + { + if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) + break; + } + mClassImage->setImageTexture ("textures\\levelup\\" + it->mId + ".dds"); + } else mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); - int level = creatureStats.getLevel ()+1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast(level)); From fc1837e2edfe2c56057ea61c8d263a0dd0577bd5 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 15 Apr 2014 19:26:43 +0200 Subject: [PATCH 83/85] fixed bug: regions can be dragged ps fuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu --- apps/opencs/model/world/columnimp.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 89fb586aa..6976b454d 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -785,7 +785,7 @@ namespace CSMWorld template struct RegionColumn : public Column { - RegionColumn() : Column (Columns::ColumnId_Region, ColumnBase::Display_String) {} + RegionColumn() : Column (Columns::ColumnId_Region, ColumnBase::Display_Region) {} virtual QVariant get (const Record& record) const { From 8fba71101c014a873f7ec412d8e590ca8413ef22 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 15 Apr 2014 20:39:19 +0200 Subject: [PATCH 84/85] removed signals --- apps/opencs/view/filter/editwidget.cpp | 9 +-------- apps/opencs/view/filter/editwidget.hpp | 6 +++--- apps/opencs/view/filter/filterbox.cpp | 10 ++++++---- apps/opencs/view/filter/filterbox.hpp | 20 ++++++++++---------- apps/opencs/view/filter/recordfilterbox.cpp | 14 ++++++++------ apps/opencs/view/filter/recordfilterbox.hpp | 8 +++++--- apps/opencs/view/world/tablesubview.cpp | 9 ++------- apps/opencs/view/world/tablesubview.hpp | 3 --- 8 files changed, 35 insertions(+), 44 deletions(-) diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index b163297f9..68e99e0de 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -193,11 +193,4 @@ std::string CSVFilter::EditWidget::generateFilter (std::pair< std::string, std:: } return ss.str(); -} - -void CSVFilter::EditWidget::useFilterRequest (const std::string& idOfFilter) -{ - clear(); - insert(QString::fromUtf8(idOfFilter.c_str())); -} - +} \ No newline at end of file diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp index e7e34b8e9..a0f9f8919 100644 --- a/apps/opencs/view/filter/editwidget.hpp +++ b/apps/opencs/view/filter/editwidget.hpp @@ -30,6 +30,9 @@ namespace CSVFilter EditWidget (CSMWorld::Data& data, QWidget *parent = 0); + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + signals: void filterChanged (boost::shared_ptr filter); @@ -47,10 +50,7 @@ namespace CSVFilter void filterRowsInserted (const QModelIndex& parent, int start, int end); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); }; } diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index 0089143e9..e588770b1 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -25,10 +25,6 @@ CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent) SIGNAL (filterChanged (boost::shared_ptr)), this, SIGNAL (recordFilterChanged (boost::shared_ptr))); - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - mRecordFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), mRecordFilterBox, SIGNAL(useFilterRequest(const std::string&))); setAcceptDrops(true); } @@ -53,3 +49,9 @@ void CSVFilter::FilterBox::dragMoveEvent (QDragMoveEvent* event) { event->accept(); } + +void CSVFilter::FilterBox::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, + Qt::DropAction action) +{ + mRecordFilterBox->createFilterRequest(filterSource, action); +} \ No newline at end of file diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp index a8aa31953..c765164e7 100644 --- a/apps/opencs/view/filter/filterbox.hpp +++ b/apps/opencs/view/filter/filterbox.hpp @@ -24,25 +24,25 @@ namespace CSVFilter RecordFilterBox *mRecordFilterBox; - void dragEnterEvent (QDragEnterEvent* event); + public: + FilterBox (CSMWorld::Data& data, QWidget *parent = 0); - void dropEvent (QDropEvent* event); + void setRecordFilter (const std::string& filter); - void dragMoveEvent(QDragMoveEvent *event); + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); - public: - FilterBox (CSMWorld::Data& data, QWidget *parent = 0); + private: + void dragEnterEvent (QDragEnterEvent* event); - void setRecordFilter (const std::string& filter); + void dropEvent (QDropEvent* event); - signals: + void dragMoveEvent(QDragMoveEvent *event); + signals: void recordFilterChanged (boost::shared_ptr filter); void recordDropped (std::vector& types, Qt::DropAction action); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); }; } diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index f15b1e17a..ec5647618 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -24,14 +24,16 @@ CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *pare connect ( mEdit, SIGNAL (filterChanged (boost::shared_ptr)), this, SIGNAL (filterChanged (boost::shared_ptr))); - - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - mEdit, SLOT(createFilterRequest(std::vector > >&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), mEdit, SLOT(useFilterRequest(const std::string&))); } void CSVFilter::RecordFilterBox::setFilter (const std::string& filter) { + mEdit->clear(); mEdit->setText (QString::fromUtf8 (filter.c_str())); -} \ No newline at end of file +} + +void CSVFilter::RecordFilterBox::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, + Qt::DropAction action) +{ + mEdit->createFilterRequest(filterSource, action); +} diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp index 8e01310a3..f4d17510b 100644 --- a/apps/opencs/view/filter/recordfilterbox.hpp +++ b/apps/opencs/view/filter/recordfilterbox.hpp @@ -31,12 +31,14 @@ namespace CSVFilter void setFilter (const std::string& filter); - signals: + void useFilterRequest(const std::string& idOfFilter); - void filterChanged (boost::shared_ptr filter); void createFilterRequest(std::vector > >& filterSource, Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); + + signals: + + void filterChanged (boost::shared_ptr filter); }; } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 7f7b1477e..a5a7e8252 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -69,11 +69,6 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect(mFilterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), this, SLOT(createFilterRequest(std::vector&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), mFilterBox, SIGNAL(useFilterRequest(const std::string&))); - - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - mFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); } void CSVWorld::TableSubView::setEditLock (bool locked) @@ -122,7 +117,7 @@ void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::Univers filterSource.push_back(pair); } - emit createFilterRequest(filterSource, action); + mFilterBox->createFilterRequest(filterSource, action); } bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) @@ -134,7 +129,7 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); if (handled) { - emit useFilterRequest(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); + mFilterBox->setRecordFilter(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); } return handled; } diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 3f82a7592..b344ba1ad 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -55,9 +55,6 @@ namespace CSVWorld signals: void cloneRequest(const std::string&, const CSMWorld::UniversalId::Type); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); private slots: From a7cece3d30d3ea43521119f72813313a580d0ef1 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 15 Apr 2014 22:34:15 +0200 Subject: [PATCH 85/85] do not generate modfiy commands on edits to change a cell to a value equal its original value before the edit --- apps/opencs/view/world/util.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 227c5c9c5..b2a32b551 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -108,7 +108,11 @@ void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemM { NastyTableModelHack hack (*model); QStyledItemDelegate::setModelData (editor, &hack, index); - mUndoStack.push (new CSMWorld::ModifyCommand (*model, index, hack.getData())); + + QVariant new_ = hack.getData(); + + if (model->data (index)!=new_) + mUndoStack.push (new CSMWorld::ModifyCommand (*model, index, new_)); } CSVWorld::CommandDelegate::CommandDelegate (QUndoStack& undoStack, QObject *parent)