From 4eaaa5e855d8a362b1385ade296835abf26f298c Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 30 Jun 2015 03:54:56 +0200 Subject: [PATCH 01/31] Increment save file version and mark it as used, will be used in next commit --- apps/openmw/mwstate/statemanagerimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 7c111a090c..3132790636 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -218,8 +218,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.setFormat (ESM::Header::CurrentFormat); + writer.setVersion(1); + // all unused - writer.setVersion(0); writer.setType(0); writer.setAuthor(""); writer.setDescription(""); From 463775060148a6b9a53676846a5ea31c4b38b423 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 30 Jun 2015 03:58:23 +0200 Subject: [PATCH 02/31] Savegame loading optimization --- apps/openmw/mwclass/creature.cpp | 30 ++++++++++++++---------------- apps/openmw/mwclass/npc.cpp | 19 +++++++++---------- components/esm/objectstate.cpp | 2 ++ components/esm/objectstate.hpp | 4 +++- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 946f024b8c..f312a41c7a 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -798,27 +798,25 @@ namespace MWClass const ESM::CreatureState& state2 = dynamic_cast (state); - ensureCustomData(ptr); - - // If we do the following instead we get a sizable speedup, but this causes compatibility issues - // with 0.30 savegames, where some state in CreatureStats was not saved yet, - // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. - /* - if (!ptr.getRefData().getCustomData()) + if (state.mVersion > 0) { - // Create a CustomData, but don't fill it from ESM records (not needed) - std::auto_ptr data (new CreatureCustomData); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new CreatureCustomData); - MWWorld::LiveCellRef *ref = ptr.get(); + MWWorld::LiveCellRef *ref = ptr.get(); - if (ref->mBase->mFlags & ESM::Creature::Weapon) - data->mContainerStore = new MWWorld::InventoryStore(); - else - data->mContainerStore = new MWWorld::ContainerStore(); + if (ref->mBase->mFlags & ESM::Creature::Weapon) + data->mContainerStore = new MWWorld::InventoryStore(); + else + data->mContainerStore = new MWWorld::ContainerStore(); - ptr.getRefData().setCustomData (data.release()); + ptr.getRefData().setCustomData (data.release()); + } } - */ + else + ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index ac0d67269d..c7b407fb88 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1233,18 +1233,17 @@ namespace MWClass const ESM::NpcState& state2 = dynamic_cast (state); - ensureCustomData(ptr); - // If we do the following instead we get a sizable speedup, but this causes compatibility issues - // with 0.30 savegames, where some state in CreatureStats was not saved yet, - // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. - /* - if (!ptr.getRefData().getCustomData()) + if (state.mVersion > 0) { - // Create a CustomData, but don't fill it from ESM records (not needed) - std::auto_ptr data (new NpcCustomData); - ptr.getRefData().setCustomData (data.release()); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new NpcCustomData); + ptr.getRefData().setCustomData (data.release()); + } } - */ + else + ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 9ef1ccf80d..8d73b10c69 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -6,6 +6,8 @@ void ESM::ObjectState::load (ESMReader &esm) { + mVersion = esm.getVer(); + mRef.loadData(esm); mHasLocals = 0; diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index d1077733a5..674bcb8fc1 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -29,7 +29,9 @@ namespace ESM // Is there any class-specific state following the ObjectState bool mHasCustomState; - ObjectState() : mHasCustomState(true) + unsigned int mVersion; + + ObjectState() : mHasCustomState(true), mVersion(0) {} /// @note Does not load the CellRef ID, it should already be loaded before calling this method From a081d402c571c88ea69d8e6044a862f781fbf3f3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 30 Jun 2015 17:26:33 +0200 Subject: [PATCH 03/31] Use the format field instead of version field --- apps/openmw/mwstate/statemanagerimp.cpp | 5 ++--- components/esm/objectstate.cpp | 2 +- components/esm/savedgame.cpp | 1 + components/esm/savedgame.hpp | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 3132790636..2c0540fbce 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -216,11 +216,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot ++iter) writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 - writer.setFormat (ESM::Header::CurrentFormat); - - writer.setVersion(1); + writer.setFormat (ESM::SavedGame::sCurrentFormat); // all unused + writer.setVersion(0); writer.setType(0); writer.setAuthor(""); writer.setDescription(""); diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 8d73b10c69..0ae690ee8a 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -6,7 +6,7 @@ void ESM::ObjectState::load (ESMReader &esm) { - mVersion = esm.getVer(); + mVersion = esm.getFormat(); mRef.loadData(esm); diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index b5e0810dbc..9cdb28766a 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -6,6 +6,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; +int ESM::SavedGame::sCurrentFormat = 1; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esm/savedgame.hpp b/components/esm/savedgame.hpp index 3e7cae775a..aa0429657b 100644 --- a/components/esm/savedgame.hpp +++ b/components/esm/savedgame.hpp @@ -15,6 +15,8 @@ namespace ESM { static unsigned int sRecordId; + static int sCurrentFormat; + struct TimeStamp { float mGameHour; From 20d03c7e39ebf9f09570be0a5a3e1029a8001bd4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 30 Jun 2015 17:26:42 +0200 Subject: [PATCH 04/31] Refuse loading save games of unknown format --- apps/openmw/mwstate/character.cpp | 3 --- apps/openmw/mwstate/statemanagerimp.cpp | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index f190565daf..fcd1ca19e9 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -29,9 +29,6 @@ void MWState::Character::addSlot (const boost::filesystem::path& path, const std ESM::ESMReader reader; reader.open (slot.mPath.string()); - if (reader.getFormat()>ESM::Header::CurrentFormat) - return; // format is too new -> ignore - if (reader.getRecName()!=ESM::REC_SAVE) return; // invalid save file -> ignore diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 2c0540fbce..192ad45fb1 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -325,8 +325,6 @@ void MWState::StateManager::loadGame(const std::string& filepath) // have to peek into the save file to get the player name ESM::ESMReader reader; reader.open (filepath); - if (reader.getFormat()>ESM::Header::CurrentFormat) - return; // format is too new -> ignore if (reader.getRecName()!=ESM::REC_SAVE) return; // invalid save file -> ignore reader.getRecHeader(); @@ -348,6 +346,9 @@ void MWState::StateManager::loadGame (const Character *character, const std::str ESM::ESMReader reader; reader.open (filepath); + if (reader.getFormat() > ESM::SavedGame::sCurrentFormat) + throw std::runtime_error("This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade to the newest OpenMW version to load this file."); + std::map contentFileMap = buildContentFileIndexMap (reader); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); From 2065e0fa2d700163bbf9bbeb8eec5e0ccf84976a Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 30 Jun 2015 17:43:22 +0200 Subject: [PATCH 05/31] Use the correct format specifier for ess-imported savegames --- apps/essimporter/importer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 65f318297b..32ad1816cf 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -327,7 +327,7 @@ namespace ESSImport ESM::ESMWriter writer; - writer.setFormat (ESM::Header::CurrentFormat); + writer.setFormat (ESM::SavedGame::sCurrentFormat); std::ofstream stream(mOutFile.c_str(), std::ios::binary); // all unused From 519fb9482aa36e9e07d544f16325aa9fee14dd01 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 2 Jul 2015 18:11:24 +0200 Subject: [PATCH 06/31] Minor cleanup --- apps/openmw/mwworld/player.cpp | 2 +- apps/openmw/mwworld/player.hpp | 8 +------- apps/openmw/mwworld/worldimp.cpp | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 0b00f211e1..48d69e4dee 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -28,7 +28,7 @@ namespace MWWorld { - Player::Player (const ESM::NPC *player, const MWBase::World& world) + Player::Player (const ESM::NPC *player) : mCellStore(0), mLastKnownExteriorPosition(0,0,0), mMarkedCell(NULL), diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index d5376c40dc..bd5a741870 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -17,12 +17,6 @@ namespace ESM class ESMReader; } -namespace MWBase -{ - class World; - class Ptr; -} - namespace Loading { class Listener; @@ -58,7 +52,7 @@ namespace MWWorld public: - Player(const ESM::NPC *player, const MWBase::World& world); + Player(const ESM::NPC *player); void saveSkillsAttributes(); void restoreSkillsAttributes(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d8f331c625..4b14ea602a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2045,7 +2045,7 @@ namespace MWWorld { const ESM::NPC *player = mStore.get().find("player"); if (!mPlayer) - mPlayer = new MWWorld::Player(player, *this); + mPlayer = new MWWorld::Player(player); else { // Remove the old CharacterController From a1432b0255dd65999d9b148c690b793735c8111b Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 2 Jul 2015 19:14:28 +0200 Subject: [PATCH 07/31] Move attackingOrSpell flag to the CharacterController --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 3 +++ apps/openmw/mwmechanics/aicombat.cpp | 5 ++--- apps/openmw/mwmechanics/character.cpp | 14 ++++++++++---- apps/openmw/mwmechanics/character.hpp | 5 +++++ apps/openmw/mwmechanics/creaturestats.cpp | 14 +------------- apps/openmw/mwmechanics/creaturestats.hpp | 1 - apps/openmw/mwworld/player.cpp | 13 ++++++++++++- apps/openmw/mwworld/player.hpp | 5 +++++ components/esm/creaturestats.cpp | 8 ++------ components/esm/creaturestats.hpp | 1 - 11 files changed, 41 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 7ef26f703c..f472830620 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -207,7 +207,7 @@ namespace MWInput if (mControlSwitch["playercontrols"]) { if (action == A_Use) - mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).setAttackingOrSpell(currentValue != 0); + mPlayer->setAttackingOrSpell(currentValue != 0); else if (action == A_Jump) mAttemptJump = (currentValue == 1.0 && previousValue == 0.0); } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 2c53c00b7c..f0b47ac7b7 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1086,6 +1086,9 @@ namespace MWMechanics iter->second->getCharacterController()->setActive(inProcessingRange); + if (iter->first == player) + iter->second->getCharacterController()->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell()); + if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) { updateActor(iter->first, duration); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 56dd11b996..35d1f5376b 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -269,8 +269,7 @@ namespace MWMechanics attack = false; } - actorClass.getCreatureStats(actor).setAttackingOrSpell(attack); - + characterController.setAttackingOrSpell(attack); float& actionCooldown = storage.mActionCooldown; actionCooldown -= duration; @@ -476,7 +475,7 @@ namespace MWMechanics movement.mPosition[1] = 0; movement.mPosition[2] = 0; readyToAttack = false; - actorClass.getCreatureStats(actor).setAttackingOrSpell(false); + characterController.setAttackingOrSpell(false); return false; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c39b7dcef9..a3d69c059e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -654,6 +654,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mSecondsOfSwimming(0) , mSecondsOfRunning(0) , mTurnAnimationThreshold(0) + , mAttackingOrSpell(false) { if(!mAnimation) return; @@ -937,7 +938,7 @@ bool CharacterController::updateCreatureState() mAnimation->disable(mCurrentWeapon); } - if(stats.getAttackingOrSpell()) + if(mAttackingOrSpell) { if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) { @@ -997,7 +998,7 @@ bool CharacterController::updateCreatureState() } } - stats.setAttackingOrSpell(false); + mAttackingOrSpell = false; } bool animPlaying = mAnimation->getInfo(mCurrentWeapon); @@ -1142,7 +1143,7 @@ bool CharacterController::updateWeaponState() float complete; bool animPlaying; - if(stats.getAttackingOrSpell()) + if(mAttackingOrSpell) { if(mUpperBodyState == UpperCharState_WeapEquiped && mHitState == CharState_None) { @@ -1152,7 +1153,7 @@ bool CharacterController::updateWeaponState() { // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation - stats.setAttackingOrSpell(false); + mAttackingOrSpell = false; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -2041,6 +2042,11 @@ bool CharacterController::isKnockedOut() const return mHitState == CharState_KnockOut; } +void CharacterController::setAttackingOrSpell(bool attackingOrSpell) +{ + mAttackingOrSpell = attackingOrSpell; +} + void CharacterController::setActive(bool active) { mAnimation->setActive(active); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index be8fb2bf64..0a8771fb48 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -180,6 +180,9 @@ class CharacterController : public MWRender::Animation::TextKeyListener float mTurnAnimationThreshold; // how long to continue playing turning animation after actor stopped turning std::string mAttackType; // slash, chop or thrust + + bool mAttackingOrSpell; + void determineAttackType(); void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false); @@ -235,6 +238,8 @@ public: bool isReadyToBlock() const; bool isKnockedOut() const; + void setAttackingOrSpell(bool attackingOrSpell); + /// @see Animation::setActive void setActive(bool active); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 5a09eb4eec..f480efc710 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -16,7 +16,7 @@ namespace MWMechanics CreatureStats::CreatureStats() : mDrawState (DrawState_Nothing), mDead (false), mDied (false), mMurdered(false), mFriendlyHits (0), - mTalkedTo (false), mAlarmed (false), mAttacked (false), mAttackingOrSpell(false), + mTalkedTo (false), mAlarmed (false), mAttacked (false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), @@ -88,11 +88,6 @@ namespace MWMechanics return mMagicEffects; } - bool CreatureStats::getAttackingOrSpell() const - { - return mAttackingOrSpell; - } - int CreatureStats::getLevel() const { return mLevel; @@ -212,11 +207,6 @@ namespace MWMechanics mMagicEffects.setModifiers(effects); } - void CreatureStats::setAttackingOrSpell(bool attackingOrSpell) - { - mAttackingOrSpell = attackingOrSpell; - } - void CreatureStats::setAiSetting (AiSetting index, Stat value) { mAiSettings[index] = value; @@ -487,7 +477,6 @@ namespace MWMechanics state.mTalkedTo = mTalkedTo; state.mAlarmed = mAlarmed; state.mAttacked = mAttacked; - state.mAttackingOrSpell = mAttackingOrSpell; // TODO: rewrite. does this really need 3 separate bools? state.mKnockdown = mKnockdown; state.mKnockdownOneFrame = mKnockdownOneFrame; @@ -534,7 +523,6 @@ namespace MWMechanics mTalkedTo = state.mTalkedTo; mAlarmed = state.mAlarmed; mAttacked = state.mAttacked; - mAttackingOrSpell = state.mAttackingOrSpell; // TODO: rewrite. does this really need 3 separate bools? mKnockdown = state.mKnockdown; mKnockdownOneFrame = state.mKnockdownOneFrame; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 0e91cd149d..5d22da7cc3 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -40,7 +40,6 @@ namespace MWMechanics bool mTalkedTo; bool mAlarmed; bool mAttacked; - bool mAttackingOrSpell; bool mKnockdown; bool mKnockdownOneFrame; bool mKnockdownOverOneFrame; diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 48d69e4dee..b17b8e1f0c 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -36,7 +36,8 @@ namespace MWWorld mForwardBackward(0), mTeleported(false), mCurrentCrimeId(-1), - mPaidCrimeId(-1) + mPaidCrimeId(-1), + mAttackingOrSpell(false) { ESM::CellRef cellRef; cellRef.blank(); @@ -216,6 +217,16 @@ namespace MWWorld mTeleported = teleported; } + void Player::setAttackingOrSpell(bool attackingOrSpell) + { + mAttackingOrSpell = attackingOrSpell; + } + + bool Player::getAttackingOrSpell() const + { + return mAttackingOrSpell; + } + bool Player::isInCombat() { return MWBase::Environment::get().getMechanicsManager()->getActorsFighting(getPlayer()).size() != 0; } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index bd5a741870..f0ae13daa9 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -50,6 +50,8 @@ namespace MWWorld MWMechanics::SkillValue mSaveSkills[ESM::Skill::Length]; MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length]; + bool mAttackingOrSpell; + public: Player(const ESM::NPC *player); @@ -98,6 +100,9 @@ namespace MWWorld bool wasTeleported() const; void setTeleported(bool teleported); + void setAttackingOrSpell(bool attackingOrSpell); + bool getAttackingOrSpell() const; + ///Checks all nearby actors to see if anyone has an aipackage against you bool isInCombat(); diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index d0fe4be631..89d865c1dc 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -39,8 +39,8 @@ void ESM::CreatureStats::load (ESMReader &esm) if (esm.isNextSub("HOST")) esm.skipHSub(); // Hostile, no longer used - mAttackingOrSpell = false; - esm.getHNOT (mAttackingOrSpell, "ATCK"); + if (esm.isNextSub("ATCK")) + esm.skipHSub(); // attackingOrSpell, no longer used mKnockdown = false; esm.getHNOT (mKnockdown, "KNCK"); @@ -149,9 +149,6 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mAttacked) esm.writeHNT ("ATKD", mAttacked); - if (mAttackingOrSpell) - esm.writeHNT ("ATCK", mAttackingOrSpell); - if (mKnockdown) esm.writeHNT ("KNCK", mKnockdown); @@ -232,7 +229,6 @@ void ESM::CreatureStats::blank() mTalkedTo = false; mAlarmed = false; mAttacked = false; - mAttackingOrSpell = false; mKnockdown = false; mKnockdownOneFrame = false; mKnockdownOverOneFrame = false; diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 3b1d199e4b..426e890559 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -45,7 +45,6 @@ namespace ESM bool mTalkedTo; bool mAlarmed; bool mAttacked; - bool mAttackingOrSpell; bool mKnockdown; bool mKnockdownOneFrame; bool mKnockdownOverOneFrame; From 6691891beeca866d13e73e1e7cdb9982f95bf61f Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 2 Jul 2015 19:16:44 +0200 Subject: [PATCH 08/31] Include cleanup --- apps/openmw/mwinput/inputmanagerimp.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index f472830620..09e0b638be 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1,7 +1,6 @@ #include "inputmanagerimp.hpp" #include -#include #include @@ -22,7 +21,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/statemanager.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" @@ -31,8 +29,6 @@ #include "../mwmechanics/npcstats.hpp" -#include "../mwdialogue/dialoguemanagerimp.hpp" - using namespace ICS; namespace MWInput From dcb8fbc69ce0948dda528731f7026b872f62c583 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 1 Jul 2015 03:21:39 +0200 Subject: [PATCH 09/31] Accurate handling of fog depth/density (Fixes #2752) --- apps/openmw/mwrender/renderingmanager.cpp | 17 ++++++++++++++--- apps/openmw/mwrender/renderingmanager.hpp | 1 + 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d85c1c0065..c861119b53 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -46,7 +46,8 @@ namespace MWRender { public: StateUpdater() - : mFogEnd(0.f) + : mFogStart(0.f) + , mFogEnd(0.f) , mWireframe(false) { } @@ -56,7 +57,6 @@ namespace MWRender osg::LightModel* lightModel = new osg::LightModel; stateset->setAttribute(lightModel, osg::StateAttribute::ON); osg::Fog* fog = new osg::Fog; - fog->setStart(1); fog->setMode(osg::Fog::LINEAR); stateset->setAttributeAndModes(fog, osg::StateAttribute::ON); if (mWireframe) @@ -75,6 +75,7 @@ namespace MWRender lightModel->setAmbientIntensity(mAmbientColor); osg::Fog* fog = static_cast(stateset->getAttribute(osg::StateAttribute::FOG)); fog->setColor(mFogColor); + fog->setStart(mFogStart); fog->setEnd(mFogEnd); } @@ -88,6 +89,11 @@ namespace MWRender mFogColor = col; } + void setFogStart(float start) + { + mFogStart = start; + } + void setFogEnd(float end) { mFogEnd = end; @@ -110,6 +116,7 @@ namespace MWRender private: osg::Vec4f mAmbientColor; osg::Vec4f mFogColor; + float mFogStart; float mFogEnd; bool mWireframe; }; @@ -118,6 +125,7 @@ namespace MWRender : mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) + , mFogDepth(0.f) , mNightEyeFactor(0.f) { osg::ref_ptr lightRoot = new SceneUtil::LightManager; @@ -338,8 +346,9 @@ namespace MWRender configureFog (cell->mAmbi.mFogDensity, color); } - void RenderingManager::configureFog(float /* fogDepth */, const osg::Vec4f &color) + void RenderingManager::configureFog(float fogDepth, const osg::Vec4f &color) { + mFogDepth = fogDepth; mFogColor = color; } @@ -364,11 +373,13 @@ namespace MWRender if (mWater->isUnderwater(cameraPos)) { setFogColor(osg::Vec4f(0.090195f, 0.115685f, 0.12745f, 1.f)); + mStateUpdater->setFogStart(0.f); mStateUpdater->setFogEnd(1000); } else { setFogColor(mFogColor); + mStateUpdater->setFogStart(mViewDistance * (1 - mFogDepth)); mStateUpdater->setFogEnd(mViewDistance); } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index fc2f5a4f38..def3ea4bba 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -190,6 +190,7 @@ namespace MWRender osg::ref_ptr mStateUpdater; + float mFogDepth; osg::Vec4f mFogColor; osg::Vec4f mAmbientColor; From bf9c62fa4224722edcc222184e66afe13f7568f6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 1 Jul 2015 03:42:04 +0200 Subject: [PATCH 10/31] Fix for some coverity scan defects --- apps/openmw/mwgui/mapwindow.hpp | 5 +++++ apps/openmw/mwrender/globalmap.hpp | 3 ++- apps/openmw/mwrender/ripplesimulation.hpp | 4 ---- apps/openmw/mwrender/sky.cpp | 9 ++++++--- components/myguiplatform/myguirendermanager.cpp | 2 ++ components/nifosg/controller.cpp | 2 ++ components/nifosg/particle.cpp | 2 +- 7 files changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index bea3d33b9f..388103b5d2 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -72,6 +72,11 @@ namespace MWGui { MarkerUserData(MWRender::LocalMap* map) : mLocalMapRender(map) + , interior(false) + , cellX(0) + , cellY(0) + , nX(0.f) + , nY(0.f) { } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index 7adb8218a7..91c17c06f1 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -88,7 +88,8 @@ namespace MWRender struct ImageDest { ImageDest() - : mFramesUntilDone(3) // wait an extra frame to ensure the draw thread has completed its frame. + : mX(0), mY(0) + , mFramesUntilDone(3) // wait an extra frame to ensure the draw thread has completed its frame. { } diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index 98c8a707dc..8e591a5dbb 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -65,10 +65,6 @@ namespace MWRender osg::ref_ptr mParticleNode; std::vector mEmitters; - - float mRippleLifeTime; - float mRippleRotSpeed; - }; } diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index dc43783ff5..db7e5462e8 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -162,6 +162,12 @@ protected: class CloudUpdater : public SceneUtil::StateSetUpdater { public: + CloudUpdater() + : mAnimationTimer(0.f) + , mOpacity(0.f) + { + } + void setAnimationTimer(float timer) { mAnimationTimer = timer; @@ -762,9 +768,6 @@ public: mat->setColorMode(osg::Material::OFF); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); } - -private: - float mAlpha; }; void SkyManager::createRain() diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 773c58a596..4979d6451b 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -165,6 +165,8 @@ public: Drawable(const Drawable ©, const osg::CopyOp ©op=osg::CopyOp::SHALLOW_COPY) : osg::Drawable(copy, copyop) , mParent(copy.mParent) + , mWriteTo(0) + , mReadFrom(0) { } diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index a314910c5b..5e7e55004e 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -407,6 +407,7 @@ FlipController::FlipController(int texSlot, float delta, std::vectorgetNodePath(); path.pop_back(); - osg::MatrixTransform* trans = dynamic_cast(node); + osg::MatrixTransform* trans = static_cast(node); osg::Matrix mat = osg::computeLocalToWorld( path ); mat.orthoNormalize(mat); // don't undo the scale From 631cec7304d0a87bfb980aed1577648f9a6cf65b Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 2 Jul 2015 17:19:07 +0200 Subject: [PATCH 11/31] Take the radius of lights into account when sorting --- components/sceneutil/lightmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index bcdb4af88e..039d556d11 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -244,7 +244,7 @@ namespace SceneUtil bool sortLights (const LightManager::LightSourceTransform* left, const LightManager::LightSourceTransform* right) { - return left->mViewBound.center().length2() < right->mViewBound.center().length2(); + return left->mViewBound.center().length2() - left->mViewBound.radius2()/4.f < right->mViewBound.center().length2() - right->mViewBound.radius2()/4.f; } void LightListCallback::operator()(osg::Node *node, osg::NodeVisitor *nv) From 672458577777ed9400f6668889c0dea00d2c4220 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 2 Jul 2015 17:19:30 +0200 Subject: [PATCH 12/31] Light magic effect Notable change compared to the old (Ogre) effect: uses the ambient instead of diffuse term (Fixes #2364) --- apps/openmw/mwrender/animation.cpp | 32 ++++++++++++++++++++++++++++++ apps/openmw/mwrender/animation.hpp | 9 ++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 5955fc4cfd..91f459ff2f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -267,6 +267,8 @@ namespace MWRender Animation::~Animation() { + setLightEffect(0.f); + if (mObjectRoot) mInsert->removeChild(mObjectRoot); } @@ -1224,6 +1226,36 @@ namespace MWRender return found->second; } + void Animation::setLightEffect(float effect) + { + if (effect == 0) + { + if (mGlowLight) + { + mInsert->removeChild(mGlowLight); + mGlowLight = NULL; + } + } + else + { + if (!mGlowLight) + { + mGlowLight = new SceneUtil::LightSource; + mGlowLight->setLight(new osg::Light); + osg::Light* light = mGlowLight->getLight(); + light->setDiffuse(osg::Vec4f(0,0,0,0)); + light->setSpecular(osg::Vec4f(0,0,0,0)); + light->setAmbient(osg::Vec4f(1.5f,1.5f,1.5f,1.f)); + mInsert->addChild(mGlowLight); + } + + effect += 3; + osg::Light* light = mGlowLight->getLight(); + mGlowLight->setRadius(effect * 66.f); + light->setLinearAttenuation(0.5f/effect); + } + } + void Animation::addControllers() { mHeadController = NULL; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 73079e9a9b..d23a629549 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -21,6 +21,11 @@ namespace NifOsg class KeyframeController; } +namespace SceneUtil +{ + class LightSource; +} + namespace MWRender { @@ -202,6 +207,8 @@ protected: float mHeadYawRadians; float mHeadPitchRadians; + osg::ref_ptr mGlowLight; + /* Sets the appropriate animations on the bone groups based on priority. */ void resetActiveGroups(); @@ -377,7 +384,7 @@ public: // TODO: move outside of this class /// Makes this object glow, by placing a Light in its center. /// @param effect Controls the radius and intensity of the light. - virtual void setLightEffect(float effect) {} + virtual void setLightEffect(float effect); virtual void setHeadPitch(float pitchRadians); virtual void setHeadYaw(float yawRadians); From 520a832a9a7072b8bf306d94bc9b1d1666e615d2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 2 Jul 2015 22:24:23 +0200 Subject: [PATCH 13/31] Remove an already resolved todo comment --- apps/openmw/mwmechanics/aicombat.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 35d1f5376b..88d4167abd 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -333,9 +333,6 @@ namespace MWMechanics // Get weapon characteristics if (actorClass.hasInventoryStore(actor)) { - // TODO: Check equipped weapon and equip a different one if we can't attack with it - // (e.g. no ammunition, or wrong type of ammunition equipped, etc. autoEquip is not very smart in this regard)) - //Get weapon speed and range MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(actorClass.getCreatureStats(actor), actorClass.getInventoryStore(actor), &weaptype); From d07b176b368ac0fd7df95c3240e27b97b6a13813 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 2 Jul 2015 22:25:19 +0200 Subject: [PATCH 14/31] Take into account hit recovery, knockdown and other animations for AiCombat attack timing --- apps/openmw/mwmechanics/aicombat.cpp | 10 +--------- apps/openmw/mwmechanics/character.cpp | 5 +++++ apps/openmw/mwmechanics/character.hpp | 2 ++ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 88d4167abd..d8b2dc9acc 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -300,10 +300,6 @@ namespace MWMechanics currentCell = actor.getCell(); } - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(actor); - if (!anim) // shouldn't happen - return false; - actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); if (actionCooldown > 0) @@ -312,11 +308,7 @@ namespace MWMechanics float rangeAttack = 0; float rangeFollow = 0; boost::shared_ptr& currentAction = storage.mCurrentAction; - // TODO: upperBodyReady() works fine for checking if we can start an attack, - // but doesn't work properly for checking if the attack is finished (as things like hit recovery or knockdown also play on the upper body) - // Only a minor problem, but can mess with the actionCooldown timer. - // To fix this the AI needs to be brought closer to the CharacterController, so we can simply check if a weapon animation is playing. - if (anim->upperBodyReady()) + if (characterController.readyToPrepareAttack()) { currentAction = prepareNextAction(actor, target); actionCooldown = currentAction->getActionCooldown(); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index a3d69c059e..ec1e0e81b8 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2047,6 +2047,11 @@ void CharacterController::setAttackingOrSpell(bool attackingOrSpell) mAttackingOrSpell = attackingOrSpell; } +bool CharacterController::readyToPrepareAttack() const +{ + return mHitState == CharState_None && mUpperBodyState <= UpperCharState_WeapEquiped; +} + void CharacterController::setActive(bool active) { mAnimation->setActive(active); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 0a8771fb48..6a5e0593df 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -240,6 +240,8 @@ public: void setAttackingOrSpell(bool attackingOrSpell); + bool readyToPrepareAttack() const; + /// @see Animation::setActive void setActive(bool active); From 82740c164501a629ec08213cad2c49b52a919b33 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 3 Jul 2015 02:29:42 +0200 Subject: [PATCH 15/31] Remove completely broken code --- apps/openmw/mwmechanics/aicombat.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index d8b2dc9acc..85a88e5465 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -640,24 +640,6 @@ namespace MWMechanics } } - // TODO: Add a parameter to vary DURATION_SAME_SPOT? - if((distToTarget > rangeAttack || followTarget) && - mObstacleCheck.check(actor, tReaction)) // check if evasive action needed - { - // probably walking into another NPC TODO: untested in combat situation - // TODO: diagonal should have same animation as walk forward - // but doesn't seem to do that? - actor.getClass().getMovementSettings(actor).mPosition[0] = 1; - actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; - // change the angle a bit, too - if(mPathFinder.isPathConstructed()) - zTurn(actor, osg::DegreesToRadians(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); - - if(followTarget) - followTarget = false; - // FIXME: can fool actors to stay behind doors, etc. - // Related to Bug#1102 and to some degree #1155 as well - } return false; } From 2385938485a1e4638151df2d0fd52515cf9e0b41 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 3 Jul 2015 02:42:40 +0200 Subject: [PATCH 16/31] Fix a typo --- apps/opencs/model/world/columns.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index d6e27caeb1..7aec68309c 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -92,7 +92,7 @@ namespace CSMWorld { ColumnId_Trainer, "Trainer" }, { ColumnId_Spellmaking, "Spellmaking" }, { ColumnId_EnchantingService, "Enchanting Service" }, - { ColumnId_RepairService, "Repair Serivce" }, + { ColumnId_RepairService, "Repair Service" }, { ColumnId_ApparatusType, "Apparatus Type" }, { ColumnId_ArmorType, "Armor Type" }, { ColumnId_Health, "Health" }, From 741e5db862d23f845dd93b96605093055b31f236 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 3 Jul 2015 05:37:18 +0200 Subject: [PATCH 17/31] Fix for non-bipedal creatures that use weapons, e.g. rieklings --- apps/openmw/mwmechanics/character.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index ec1e0e81b8..cf963e8aa3 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1789,7 +1789,8 @@ void CharacterController::update(float duration) } } - if(cls.isBipedal(mPtr)) + // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. + if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) forcestateupdate = updateWeaponState() || forcestateupdate; else forcestateupdate = updateCreatureState() || forcestateupdate; From a7bd050928b746386d5ba35425eb0d1c040a4896 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 3 Jul 2015 05:58:12 +0200 Subject: [PATCH 18/31] Accurate attack timings in AiCombat --- apps/openmw/mwmechanics/aicombat.cpp | 173 +++----------------------- apps/openmw/mwmechanics/character.cpp | 18 +++ apps/openmw/mwmechanics/character.hpp | 4 +- 3 files changed, 38 insertions(+), 157 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 85a88e5465..c0d2bc90d4 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -31,8 +31,6 @@ namespace //chooses an attack depending on probability to avoid uniformity ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); - void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]); - osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength); @@ -83,7 +81,6 @@ namespace MWMechanics /// \brief This class holds the variables AiCombat needs which are deleted if the package becomes inactive. struct AiCombatStorage : AiTemporaryBase { - float mTimerAttack; float mTimerReact; float mTimerCombatMove; bool mReadyToAttack; @@ -95,15 +92,12 @@ namespace MWMechanics boost::shared_ptr mCurrentAction; float mActionCooldown; float mStrength; - float mMinMaxAttackDuration[3][2]; - bool mMinMaxAttackDurationInitialised; bool mForceNoShortcut; ESM::Position mShortcutFailPos; osg::Vec3f mLastActorPos; MWMechanics::Movement mMovement; AiCombatStorage(): - mTimerAttack(0), mTimerReact(0), mTimerCombatMove(0), mReadyToAttack(false), @@ -115,7 +109,6 @@ namespace MWMechanics mCurrentAction(), mActionCooldown(0), mStrength(), - mMinMaxAttackDurationInitialised(false), mForceNoShortcut(false), mLastActorPos(0,0,0), mMovement(){} @@ -233,41 +226,12 @@ namespace MWMechanics { if(smoothTurn(actor, osg::DegreesToRadians(movement.mRotation[0]), 0)) movement.mRotation[0] = 0; } - - float attacksPeriod = 1.0f; - - ESM::Weapon::AttackType attackType; - - - bool& attack = storage.mAttack; bool& readyToAttack = storage.mReadyToAttack; - float& timerAttack = storage.mTimerAttack; - - bool& minMaxAttackDurationInitialised = storage.mMinMaxAttackDurationInitialised; - float (&minMaxAttackDuration)[3][2] = storage.mMinMaxAttackDuration; - - if(readyToAttack) - { - if (!minMaxAttackDurationInitialised) - { - // TODO: this must be updated when a different weapon is equipped - // TODO: it would be much easier to ask the CharacterController about the current % completion of the weapon wind-up animation - getMinMaxAttackDuration(actor, minMaxAttackDuration); - minMaxAttackDurationInitialised = true; - } - - if (timerAttack < 0) attack = false; - - timerAttack -= duration; - } - else - { - timerAttack = -attacksPeriod; + if (attack && (characterController.getAttackStrength() >= storage.mStrength || characterController.readyToPrepareAttack())) attack = false; - } characterController.setAttackingOrSpell(attack); @@ -372,32 +336,26 @@ namespace MWMechanics } - float& strength = storage.mStrength; + float& strength = storage.mStrength; // start new attack - if(readyToAttack) + if(readyToAttack && characterController.readyToStartAttack()) { - if(timerAttack <= -attacksPeriod) + attack = true; // attack starts just now + characterController.setAttackingOrSpell(attack); + + if (!distantCombat) + chooseBestAttack(weapon, movement); + + strength = Misc::Rng::rollClosedProbability(); + + //say a provoking combat phrase + if (actor.getClass().isNpc()) { - attack = true; // attack starts just now - - if (!distantCombat) attackType = chooseBestAttack(weapon, movement); - else attackType = ESM::Weapon::AT_Chop; // cause it's =0 - - strength = Misc::Rng::rollClosedProbability(); - - // Note: may be 0 for some animations - timerAttack = minMaxAttackDuration[attackType][0] + - (minMaxAttackDuration[attackType][1] - minMaxAttackDuration[attackType][0]) * strength; - - //say a provoking combat phrase - if (actor.getClass().isNpc()) + const MWWorld::ESMStore &store = world->getStore(); + int chance = store.get().find("iVoiceAttackOdds")->getInt(); + if (Misc::Rng::roll0to99() < chance) { - const MWWorld::ESMStore &store = world->getStore(); - int chance = store.get().find("iVoiceAttackOdds")->getInt(); - if (Misc::Rng::roll0to99() < chance) - { - MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); - } + MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } } } @@ -607,39 +565,6 @@ namespace MWMechanics readyToAttack = false; } - if(!isStuck && distToTarget > rangeAttack && !distantCombat) - { - //special run attack; it shouldn't affect melee combat tactics - if(actorClass.getMovementSettings(actor).mPosition[1] == 1) - { - /* check if actor can overcome the distance = distToTarget - attackerWeapRange - less than in time of swinging with weapon (t_swing), then start attacking - */ - float speed1 = actorClass.getSpeed(actor); - float speed2 = target.getClass().getSpeed(target); - if(target.getClass().getMovementSettings(target).mPosition[0] == 0 - && target.getClass().getMovementSettings(target).mPosition[1] == 0) - speed2 = 0; - - float s1 = distToTarget - weapRange; - float t = s1/speed1; - float s2 = speed2 * t; - float t_swing = - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0] + - (minMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * Misc::Rng::rollClosedProbability(); - - if (t + s2/speed1 <= t_swing) - { - readyToAttack = true; - if(timerAttack <= -attacksPeriod) - { - timerAttack = t_swing; - attack = true; - } - } - } - } - return false; } @@ -765,70 +690,6 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: return attackType; } -void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]) -{ - if (!actor.getClass().hasInventoryStore(actor)) // creatures - { - fMinMaxDurations[0][0] = fMinMaxDurations[0][1] = 0.1f; - fMinMaxDurations[1][0] = fMinMaxDurations[1][1] = 0.1f; - fMinMaxDurations[2][0] = fMinMaxDurations[2][1] = 0.1f; - - return; - } - - // get weapon information: type and speed - const ESM::Weapon *weapon = NULL; - MWMechanics::WeaponType weaptype = MWMechanics::WeapType_None; - - MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(actor.getClass().getCreatureStats(actor), actor.getClass().getInventoryStore(actor), &weaptype); - - float weapSpeed; - if (weaptype != MWMechanics::WeapType_HandToHand - && weaptype != MWMechanics::WeapType_Spell - && weaptype != MWMechanics::WeapType_None) - { - weapon = weaponSlot->get()->mBase; - weapSpeed = weapon->mData.mSpeed; - } - else weapSpeed = 1.0f; - - MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(actor); - - std::string weapGroup; - MWMechanics::getWeaponGroup(weaptype, weapGroup); - weapGroup = weapGroup + ": "; - - bool bRangedWeap = (weaptype >= MWMechanics::WeapType_BowAndArrow && weaptype <= MWMechanics::WeapType_Thrown); - - const char *attackType[] = {"chop ", "slash ", "thrust ", "shoot "}; - - std::string textKey = "start"; - std::string textKey2; - - // get durations for each attack type - for (int i = 0; i < (bRangedWeap ? 1 : 3); i++) - { - float start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey); - - if (start1 < 0) - { - fMinMaxDurations[i][0] = fMinMaxDurations[i][1] = 0.1f; - continue; - } - - textKey2 = "min attack"; - float start2 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2); - - fMinMaxDurations[i][0] = (start2 - start1) / weapSpeed; - - textKey2 = "max attack"; - start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2); - - fMinMaxDurations[i][1] = fMinMaxDurations[i][0] + (start1 - start2) / weapSpeed; - } -} - osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength) { diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index cf963e8aa3..4b2ce9f4cb 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1259,6 +1259,8 @@ bool CharacterController::updateWeaponState() } animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && mHitState != CharState_KnockDown) + mAttackStrength = complete; } else { @@ -2053,6 +2055,22 @@ bool CharacterController::readyToPrepareAttack() const return mHitState == CharState_None && mUpperBodyState <= UpperCharState_WeapEquiped; } +bool CharacterController::readyToStartAttack() const +{ + if (mHitState != CharState_None) + return false; + + if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) + return mUpperBodyState == UpperCharState_WeapEquiped; + else + return mUpperBodyState == UpperCharState_Nothing; +} + +float CharacterController::getAttackStrength() const +{ + return mAttackStrength; +} + void CharacterController::setActive(bool active) { mAnimation->setActive(active); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 6a5e0593df..b239b4a925 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -241,6 +241,9 @@ public: void setAttackingOrSpell(bool attackingOrSpell); bool readyToPrepareAttack() const; + bool readyToStartAttack() const; + + float getAttackStrength() const; /// @see Animation::setActive void setActive(bool active); @@ -249,7 +252,6 @@ public: void setHeadTrackTarget(const MWWorld::Ptr& target); }; - void getWeaponGroup(WeaponType weaptype, std::string &group); MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype); } From 382cdb8c60536a4808b08e3af294d965dfa8f71c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 3 Jul 2015 10:45:08 +0200 Subject: [PATCH 19/31] implemented sorting in report views (Fixes #2540) --- apps/opencs/view/tools/reporttable.cpp | 59 ++++++++++++++++++-------- apps/opencs/view/tools/reporttable.hpp | 17 +++++--- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index ca6b0dabfb..550c53969e 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "../../model/tools/reportmodel.hpp" @@ -23,7 +24,7 @@ namespace CSVTools public: RichTextDelegate (QObject *parent = 0); - + virtual void paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; }; @@ -63,7 +64,7 @@ void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - QString hint = mModel->data (mModel->index (iter->row(), 2)).toString(); + QString hint = mProxyModel->data (mProxyModel->index (iter->row(), 2)).toString(); if (!hint.isEmpty() && hint[0]=='R') { @@ -78,7 +79,7 @@ void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) if (mRefreshAction) menu.addAction (mRefreshAction); - + menu.exec (event->globalPos()); } @@ -106,14 +107,14 @@ void CSVTools::ReportTable::mouseDoubleClickEvent (QMouseEvent *event) event->accept(); return; } - + switch (iter->second) { case Action_None: event->accept(); break; - + case Action_Edit: event->accept(); @@ -152,7 +153,10 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); - setModel (mModel); + mProxyModel = new QSortFilterProxyModel (this); + mProxyModel->setSourceModel (mModel); + + setModel (mProxyModel); setColumnHidden (2, true); mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate (0, @@ -162,7 +166,7 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, if (richTextDescription) setItemDelegateForColumn (mModel->columnCount()-1, new RichTextDelegate (this)); - + mShowAction = new QAction (tr ("Show"), this); connect (mShowAction, SIGNAL (triggered()), this, SLOT (showSelection())); addAction (mShowAction); @@ -182,10 +186,10 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, connect (mRefreshAction, SIGNAL (triggered()), this, SIGNAL (refreshRequest())); addAction (mRefreshAction); } - + mDoubleClickActions.insert (std::make_pair (Qt::NoModifier, Action_Edit)); mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_Remove)); - mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_EditAndRemove)); + mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_EditAndRemove)); } std::vector CSVTools::ReportTable::getDraggedRecords() const @@ -197,7 +201,7 @@ std::vector CSVTools::ReportTable::getDraggedRecords() co for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - ids.push_back (mModel->getUniversalId (iter->row())); + ids.push_back (mModel->getUniversalId (mProxyModel->mapToSource (*iter).row())); } return ids; @@ -234,7 +238,7 @@ void CSVTools::ReportTable::updateUserSetting (const QString& name, const QStrin mDoubleClickActions[modifiers] = action; return; - } + } } std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const @@ -245,13 +249,22 @@ std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const { QModelIndexList selectedRows = selectionModel()->selectedRows(); + std::vector rows; + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - QString hint = mModel->data (mModel->index (iter->row(), 2)).toString(); + rows.push_back (mProxyModel->mapToSource (*iter).row()); + } + + std::sort (rows.begin(), rows.end()); + + for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) + { + QString hint = mModel->data (mModel->index (*iter, 2)).toString(); if (!hint.isEmpty() && hint[0]=='R') - indices.push_back (iter->row()); + indices.push_back (*iter); } } else @@ -272,25 +285,35 @@ void CSVTools::ReportTable::flagAsReplaced (int index) { mModel->flagAsReplaced (index); } - + void CSVTools::ReportTable::showSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) - emit editRequest (mModel->getUniversalId (iter->row()), mModel->getHint (iter->row())); + { + int row = mProxyModel->mapToSource (*iter).row(); + emit editRequest (mModel->getUniversalId (row), mModel->getHint (row)); + } } void CSVTools::ReportTable::removeSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); - std::reverse (selectedRows.begin(), selectedRows.end()); + std::vector rows; - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + for (QModelIndexList::iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) - mModel->removeRows (iter->row(), 1); + { + rows.push_back (mProxyModel->mapToSource (*iter).row()); + } + + std::sort (rows.begin(), rows.end()); + + for (std::vector::const_reverse_iterator iter (rows.rbegin()); iter!=rows.rend(); ++iter) + mProxyModel->removeRows (*iter, 1); selectionModel()->clear(); } diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index e19b327e45..c847b2d478 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -6,6 +6,7 @@ #include "../world/dragrecordtable.hpp" class QAction; +class QSortFilterProxyModel; namespace CSMTools { @@ -30,7 +31,8 @@ namespace CSVTools Action_Remove, Action_EditAndRemove }; - + + QSortFilterProxyModel *mProxyModel; CSMTools::ReportModel *mModel; CSVWorld::CommandDelegate *mIdTypeDelegate; QAction *mShowAction; @@ -63,11 +65,14 @@ namespace CSVTools void clear(); - // Return indices of rows that are suitable for replacement. - // - // \param selection Only list selected rows. + /// Return indices of rows that are suitable for replacement. + /// + /// \param selection Only list selected rows. + /// + /// \return rows in the original model std::vector getReplaceIndices (bool selection) const; + /// \param index row in the original model void flagAsReplaced (int index); private slots: @@ -78,8 +83,8 @@ namespace CSVTools public slots: - void stateChanged (int state, CSMDoc::Document *document); - + void stateChanged (int state, CSMDoc::Document *document); + signals: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); From 47b66b57ae52972ff514509270b1d12cd0a23beb Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 15:37:10 +0300 Subject: [PATCH 20/31] Create a context menu handler for dialogue editors with ID information --- apps/opencs/view/world/dialoguesubview.cpp | 69 ++++++++++++++++++++++ apps/opencs/view/world/dialoguesubview.hpp | 24 ++++++++ 2 files changed, 93 insertions(+) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 1d3eb53139..70a3cd37c0 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "../../model/world/nestedtableproxymodel.hpp" #include "../../model/world/columnbase.hpp" @@ -314,6 +315,74 @@ CSVWorld::DialogueDelegateDispatcher::~DialogueDelegateDispatcher() } } + +CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display) + : QObject(widget), + mWidget(widget), + mIdType(CSMWorld::TableMimeData::convertEnums(display)) +{ + Q_ASSERT(mWidget != NULL); + Q_ASSERT(CSMWorld::ColumnBase::isId(display)); + Q_ASSERT(mIdType != CSMWorld::UniversalId::Type_None); + + mWidget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(mWidget, + SIGNAL(customContextMenuRequested(const QPoint &)), + this, + SLOT(showContextMenu(const QPoint &))); + + mEditIdAction = new QAction(this); + + QLineEdit *lineEdit = qobject_cast(mWidget); + if (lineEdit != NULL) + { + mContextMenu = lineEdit->createStandardContextMenu(); + mContextMenu->setParent(mWidget); + + QAction *action = mContextMenu->actions().first(); + mContextMenu->insertAction(action, mEditIdAction); + mContextMenu->insertSeparator(action); + } + else + { + mContextMenu = new QMenu(mWidget); + mContextMenu->addAction(mEditIdAction); + } +} + +QString CSVWorld::IdContextMenu::getWidgetValue() const +{ + static QLineEdit *lineEdit = qobject_cast(mWidget); + static QLabel *label = qobject_cast(mWidget); + + QString value = ""; + if (lineEdit != NULL) + { + value = lineEdit->text(); + } + else if (label != NULL) + { + value = label->text(); + } + return value; +} + +void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) +{ + QString value = getWidgetValue(); + if (!value.isEmpty()) + { + mEditIdAction->setText("Edit '" + value + "'"); + + QAction *selectedAction = mContextMenu->exec(mWidget->mapToGlobal(pos)); + if (selectedAction != NULL && selectedAction == mEditIdAction) + { + CSMWorld::UniversalId editId(mIdType, value.toUtf8().constData()); + emit editIdRequest(editId, ""); + } + } +} + /* =============================================================EditWidget===================================================== */ diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index be58be5ade..f33c6d9d2a 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -11,12 +11,14 @@ #include "../../model/world/columnbase.hpp" #include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/universalid.hpp" class QDataWidgetMapper; class QSize; class QEvent; class QLabel; class QVBoxLayout; +class QMenu; namespace CSMWorld { @@ -149,6 +151,28 @@ namespace CSVWorld CSMWorld::ColumnBase::Display display); }; + class IdContextMenu : public QObject + { + Q_OBJECT + + QWidget *mWidget; + CSMWorld::UniversalId::Type mIdType; + + QMenu *mContextMenu; + QAction *mEditIdAction; + + QString getWidgetValue() const; + + public: + IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display); + + private slots: + void showContextMenu(const QPoint &pos); + + signals: + void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); + }; + class EditWidget : public QScrollArea { Q_OBJECT From e8e34f37dd04e25d6fa6ffe26d286a5e95dc5edf Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 16:24:08 +0300 Subject: [PATCH 21/31] Edit 'ID' action is available in dialogue sub-views (for LineEdits and Labels) --- apps/opencs/view/world/dialoguesubview.cpp | 33 ++++++++++++++++------ apps/opencs/view/world/dialoguesubview.hpp | 4 +++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 70a3cd37c0..da325f6c3e 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -332,6 +332,7 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di SLOT(showContextMenu(const QPoint &))); mEditIdAction = new QAction(this); + connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editIdRequest())); QLineEdit *lineEdit = qobject_cast(mWidget); if (lineEdit != NULL) @@ -352,8 +353,8 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di QString CSVWorld::IdContextMenu::getWidgetValue() const { - static QLineEdit *lineEdit = qobject_cast(mWidget); - static QLabel *label = qobject_cast(mWidget); + QLineEdit *lineEdit = qobject_cast(mWidget); + QLabel *label = qobject_cast(mWidget); QString value = ""; if (lineEdit != NULL) @@ -373,16 +374,16 @@ void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) if (!value.isEmpty()) { mEditIdAction->setText("Edit '" + value + "'"); - - QAction *selectedAction = mContextMenu->exec(mWidget->mapToGlobal(pos)); - if (selectedAction != NULL && selectedAction == mEditIdAction) - { - CSMWorld::UniversalId editId(mIdType, value.toUtf8().constData()); - emit editIdRequest(editId, ""); - } + mContextMenu->exec(mWidget->mapToGlobal(pos)); } } +void CSVWorld::IdContextMenu::editIdRequest() +{ + CSMWorld::UniversalId editId(mIdType, getWidgetValue().toUtf8().constData()); + emit editIdRequest(editId, ""); +} + /* =============================================================EditWidget===================================================== */ @@ -557,6 +558,15 @@ void CSVWorld::EditWidget::remake(int row) editor->setEnabled(false); label->setEnabled(false); } + + if (CSMWorld::ColumnBase::isId(display)) + { + IdContextMenu *menu = new IdContextMenu(editor, display); + connect(menu, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); + } } } else @@ -676,6 +686,11 @@ CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::Universa mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); dataChanged(mTable->getModelIndex (getUniversalId().getId(), 0)); + + connect(mEditWidget, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(focusId(const CSMWorld::UniversalId &, const std::string &))); } void CSVWorld::SimpleDialogueSubView::setEditLock (bool locked) diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index f33c6d9d2a..f4331abb25 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -168,6 +168,7 @@ namespace CSVWorld private slots: void showContextMenu(const QPoint &pos); + void editIdRequest(); signals: void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); @@ -195,6 +196,9 @@ namespace CSVWorld virtual ~EditWidget(); void remake(int row); + + signals: + void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; class SimpleDialogueSubView : public CSVDoc::SubView From 149cb9d00489bd43631f4ec14e03189487aa4498 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 16:51:47 +0300 Subject: [PATCH 22/31] IdContextMenu: if the ID field is empty show the standard context menu (if available) --- apps/opencs/view/world/dialoguesubview.cpp | 48 ++++++++++++++++++---- apps/opencs/view/world/dialoguesubview.hpp | 2 + 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index da325f6c3e..bfd01b14cf 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -338,16 +338,10 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di if (lineEdit != NULL) { mContextMenu = lineEdit->createStandardContextMenu(); - mContextMenu->setParent(mWidget); - - QAction *action = mContextMenu->actions().first(); - mContextMenu->insertAction(action, mEditIdAction); - mContextMenu->insertSeparator(action); } else { mContextMenu = new QMenu(mWidget); - mContextMenu->addAction(mEditIdAction); } } @@ -368,12 +362,52 @@ QString CSVWorld::IdContextMenu::getWidgetValue() const return value; } +void CSVWorld::IdContextMenu::addEditIdActionToMenu(const QString &text) +{ + mEditIdAction->setText(text); + if (mContextMenu->actions().isEmpty()) + { + mContextMenu->addAction(mEditIdAction); + } + else + { + QAction *action = mContextMenu->actions().first(); + mContextMenu->insertAction(action, mEditIdAction); + mContextMenu->insertSeparator(action); + } +} + +void CSVWorld::IdContextMenu::removeEditIdActionFromMenu() +{ + if (mContextMenu->actions().isEmpty()) + { + return; + } + + if (mContextMenu->actions().first() == mEditIdAction) + { + mContextMenu->removeAction(mEditIdAction); + if (!mContextMenu->actions().isEmpty() && mContextMenu->actions().first()->isSeparator()) + { + mContextMenu->removeAction(mContextMenu->actions().first()); + } + } +} + void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) { QString value = getWidgetValue(); if (!value.isEmpty()) { - mEditIdAction->setText("Edit '" + value + "'"); + addEditIdActionToMenu("Edit '" + value + "'"); + } + else + { + removeEditIdActionFromMenu(); + } + + if (!mContextMenu->actions().isEmpty()) + { mContextMenu->exec(mWidget->mapToGlobal(pos)); } } diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index f4331abb25..ddb6e50566 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -162,6 +162,8 @@ namespace CSVWorld QAction *mEditIdAction; QString getWidgetValue() const; + void addEditIdActionToMenu(const QString &text); + void removeEditIdActionFromMenu(); public: IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display); From 61ab0ba4821a18c77ba732707f4c370b463f6e12 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 18:48:13 +0300 Subject: [PATCH 23/31] Prevent the Edit 'ID' action for the current ID of the dialogue subview --- apps/opencs/view/world/dialoguesubview.cpp | 13 ++++++++++++- apps/opencs/view/world/dialoguesubview.hpp | 6 ++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index bfd01b14cf..505f842273 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -345,6 +345,11 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di } } +void CSVWorld::IdContextMenu::excludeId(const std::string &id) +{ + mExcludedIds.insert(id); +} + QString CSVWorld::IdContextMenu::getWidgetValue() const { QLineEdit *lineEdit = qobject_cast(mWidget); @@ -397,7 +402,8 @@ void CSVWorld::IdContextMenu::removeEditIdActionFromMenu() void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) { QString value = getWidgetValue(); - if (!value.isEmpty()) + bool isExcludedId = mExcludedIds.find(value.toUtf8().constData()) != mExcludedIds.end(); + if (!value.isEmpty() && !isExcludedId) { addEditIdActionToMenu("Edit '" + value + "'"); } @@ -595,7 +601,12 @@ void CSVWorld::EditWidget::remake(int row) if (CSMWorld::ColumnBase::isId(display)) { + int idColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + QString id = mTable->data(mTable->index(row, idColumn)).toString(); + IdContextMenu *menu = new IdContextMenu(editor, display); + // Current ID is already opened, so no need to create Edit 'ID' action for it + menu->excludeId(id.toUtf8().constData()); connect(menu, SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), this, diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index ddb6e50566..91be5588f4 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -1,6 +1,7 @@ #ifndef CSV_WORLD_DIALOGUESUBVIEW_H #define CSV_WORLD_DIALOGUESUBVIEW_H +#include #include #include @@ -151,12 +152,15 @@ namespace CSVWorld CSMWorld::ColumnBase::Display display); }; + /// A context menu with "Edit 'ID'" action for editors in the dialogue subview class IdContextMenu : public QObject { Q_OBJECT QWidget *mWidget; CSMWorld::UniversalId::Type mIdType; + std::set mExcludedIds; + ///< A list of IDs that should not have the Edit 'ID' action. QMenu *mContextMenu; QAction *mEditIdAction; @@ -168,6 +172,8 @@ namespace CSVWorld public: IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display); + void excludeId(const std::string &id); + private slots: void showContextMenu(const QPoint &pos); void editIdRequest(); From d73fd471c3948b1e6b3db1eefa75a7273869f9c3 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 19:07:44 +0300 Subject: [PATCH 24/31] IdContextMenu: don't add Edit action if it's already in the context menu --- apps/opencs/view/world/dialoguesubview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 505f842273..283cdfa58a 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -374,7 +374,7 @@ void CSVWorld::IdContextMenu::addEditIdActionToMenu(const QString &text) { mContextMenu->addAction(mEditIdAction); } - else + else if (mContextMenu->actions().first() != mEditIdAction) { QAction *action = mContextMenu->actions().first(); mContextMenu->insertAction(action, mEditIdAction); From 6bcea21e144467b0e0cc2709341e66917aab49f9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 4 Jul 2015 05:19:05 +0200 Subject: [PATCH 25/31] Use fCombatDelayCreature, fCombatDelayNpc for random delays between aicombat attacks (Bug #1876) --- apps/openmw/mwmechanics/aicombat.cpp | 38 ++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index c0d2bc90d4..4eeea6f1f6 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -81,6 +81,7 @@ namespace MWMechanics /// \brief This class holds the variables AiCombat needs which are deleted if the package becomes inactive. struct AiCombatStorage : AiTemporaryBase { + float mAttackCooldown; float mTimerReact; float mTimerCombatMove; bool mReadyToAttack; @@ -98,6 +99,7 @@ namespace MWMechanics MWMechanics::Movement mMovement; AiCombatStorage(): + mAttackCooldown(0), mTimerReact(0), mTimerCombatMove(0), mReadyToAttack(false), @@ -340,24 +342,34 @@ namespace MWMechanics // start new attack if(readyToAttack && characterController.readyToStartAttack()) { - attack = true; // attack starts just now - characterController.setAttackingOrSpell(attack); - - if (!distantCombat) - chooseBestAttack(weapon, movement); - - strength = Misc::Rng::rollClosedProbability(); - - //say a provoking combat phrase - if (actor.getClass().isNpc()) + if (storage.mAttackCooldown <= 0) { + attack = true; // attack starts just now + characterController.setAttackingOrSpell(attack); + + if (!distantCombat) + chooseBestAttack(weapon, movement); + + strength = Misc::Rng::rollClosedProbability(); + const MWWorld::ESMStore &store = world->getStore(); - int chance = store.get().find("iVoiceAttackOdds")->getInt(); - if (Misc::Rng::roll0to99() < chance) + + //say a provoking combat phrase + if (actor.getClass().isNpc()) { - MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); + int chance = store.get().find("iVoiceAttackOdds")->getInt(); + if (Misc::Rng::roll0to99() < chance) + { + MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); + } } + float baseDelay = store.get().find("fCombatDelayCreature")->getFloat(); + if (actor.getClass().isNpc()) + baseDelay = store.get().find("fCombatDelayNPC")->getFloat(); + storage.mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9); } + else + storage.mAttackCooldown -= tReaction; } From 6b348451686674e48aa1140628514afc505d8b0f Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 18:49:32 +0300 Subject: [PATCH 26/31] Create a separate class for Edit 'ID' action to use in tables' context menus --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/tableeditidaction.cpp | 42 ++++++++++++++++++++ apps/opencs/view/world/tableeditidaction.hpp | 31 +++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/view/world/tableeditidaction.cpp create mode 100644 apps/opencs/view/world/tableeditidaction.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index dc042cb997..4c7801d461 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -64,7 +64,7 @@ opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable - dialoguespinbox recordbuttonbar + dialoguespinbox recordbuttonbar tableeditidaction ) opencs_units_noqt (view/world diff --git a/apps/opencs/view/world/tableeditidaction.cpp b/apps/opencs/view/world/tableeditidaction.cpp new file mode 100644 index 0000000000..7ce726e412 --- /dev/null +++ b/apps/opencs/view/world/tableeditidaction.cpp @@ -0,0 +1,42 @@ +#include "tableeditidaction.hpp" + +#include + +#include "../../model/world/tablemimedata.hpp" + +CSVWorld::TableEditIdAction::CellData CSVWorld::TableEditIdAction::getCellData(int row, int column) const +{ + QModelIndex index = mTable.model()->index(row, column); + if (index.isValid()) + { + QVariant display = mTable.model()->data(index, CSMWorld::ColumnBase::Role_Display); + QString value = mTable.model()->data(index).toString(); + return std::make_pair(static_cast(display.toInt()), value); + } + return std::make_pair(CSMWorld::ColumnBase::Display_None, ""); +} + +CSVWorld::TableEditIdAction::TableEditIdAction(const QTableView &table, QWidget *parent) + : QAction(parent), + mTable(table), + mCurrentId(CSMWorld::UniversalId::Type_None) +{} + +void CSVWorld::TableEditIdAction::setCell(int row, int column) +{ + CellData data = getCellData(row, column); + mCurrentId = CSMWorld::UniversalId(CSMWorld::TableMimeData::convertEnums(data.first), + data.second.toUtf8().constData()); + setText("Edit '" + data.second + "'"); +} + +CSMWorld::UniversalId CSVWorld::TableEditIdAction::getCurrentId() const +{ + return mCurrentId; +} + +bool CSVWorld::TableEditIdAction::isValidIdCell(int row, int column) const +{ + CellData data = getCellData(row, column); + return CSMWorld::ColumnBase::isId(data.first) && !data.second.isEmpty(); +} diff --git a/apps/opencs/view/world/tableeditidaction.hpp b/apps/opencs/view/world/tableeditidaction.hpp new file mode 100644 index 0000000000..f2cf0b7bd0 --- /dev/null +++ b/apps/opencs/view/world/tableeditidaction.hpp @@ -0,0 +1,31 @@ +#ifndef CSVWORLD_TABLEEDITIDACTION_HPP +#define CSVWORLD_TABLEEDITIDACTION_HPP + +#include + +#include "../../model/world/columnbase.hpp" +#include "../../model/world/universalid.hpp" + +class QTableView; + +namespace CSVWorld +{ + class TableEditIdAction : public QAction + { + const QTableView &mTable; + CSMWorld::UniversalId mCurrentId; + + typedef std::pair CellData; + CellData getCellData(int row, int column) const; + + public: + TableEditIdAction(const QTableView &table, QWidget *parent = 0); + + void setCell(int row, int column); + + CSMWorld::UniversalId getCurrentId() const; + bool isValidIdCell(int row, int column) const; + }; +} + +#endif From 464e674a817e9ecb5c034953977cdeee6c6037db Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 18:51:39 +0300 Subject: [PATCH 27/31] Rework Table (view) code to use a TableEditIdAction --- apps/opencs/view/world/table.cpp | 43 +++++++++----------------------- apps/opencs/view/world/table.hpp | 4 +-- 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index ba691131b0..74343a5f63 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -27,6 +27,7 @@ #include "../../model/settings/usersettings.hpp" #include "recordstatusdelegate.hpp" +#include "tableeditidaction.hpp" #include "util.hpp" void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) @@ -58,33 +59,13 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) /// \todo add menu items for select all and clear selection + int currentRow = rowAt(event->y()); + int currentColumn = columnAt(event->x()); + if (mEditIdAction->isValidIdCell(currentRow, currentColumn)) { - // Request UniversalId editing from table columns. - - int currRow = rowAt( event->y() ), - currCol = columnAt( event->x() ); - - currRow = mProxyModel->mapToSource(mProxyModel->index( currRow, 0 )).row(); - - CSMWorld::ColumnBase::Display colDisplay = - static_cast( - mModel->headerData( - currCol, - Qt::Horizontal, - CSMWorld::ColumnBase::Role_Display ).toInt()); - - QString cellData = mModel->data(mModel->index( currRow, currCol )).toString(); - CSMWorld::UniversalId::Type colType = CSMWorld::TableMimeData::convertEnums( colDisplay ); - - if ( !cellData.isEmpty() - && colType != CSMWorld::UniversalId::Type_None ) - { - mEditCellAction->setText(tr("Edit '").append(cellData).append("'")); - - menu.addAction( mEditCellAction ); - - mEditCellId = CSMWorld::UniversalId( colType, cellData.toUtf8().constData() ); - } + mEditIdAction->setCell(currentRow, currentColumn); + menu.addAction(mEditIdAction); + menu.addSeparator(); } if (!mEditLock && !(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) @@ -363,10 +344,6 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); addAction (mMoveDownAction); - mEditCellAction = new QAction( tr("Edit Cell"), this ); - connect( mEditCellAction, SIGNAL(triggered()), this, SLOT(editCell()) ); - addAction( mEditCellAction ); - mViewAction = new QAction (tr ("View"), this); connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord())); addAction (mViewAction); @@ -387,6 +364,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mExtendedRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedRevert())); addAction (mExtendedRevertAction); + mEditIdAction = new TableEditIdAction (*this, this); + connect (mEditIdAction, SIGNAL (triggered()), this, SLOT (editCell())); + addAction (mEditIdAction); + connect (mProxyModel, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); @@ -522,7 +503,7 @@ void CSVWorld::Table::moveDownRecord() void CSVWorld::Table::editCell() { - emit editRequest( mEditCellId, std::string() ); + emit editRequest(mEditIdAction->getCurrentId(), ""); } void CSVWorld::Table::viewRecord() diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 38fcd83bd6..adacd3a9d7 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -30,6 +30,7 @@ namespace CSMWorld namespace CSVWorld { class CommandDelegate; + class TableEditIdAction; ///< Table widget class Table : public DragRecordTable @@ -57,15 +58,14 @@ namespace CSVWorld QAction *mMoveUpAction; QAction *mMoveDownAction; QAction *mViewAction; - QAction *mEditCellAction; QAction *mPreviewAction; QAction *mExtendedDeleteAction; QAction *mExtendedRevertAction; + TableEditIdAction *mEditIdAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTableBase *mModel; int mRecordStatusDisplay; CSMWorld::CommandDispatcher *mDispatcher; - CSMWorld::UniversalId mEditCellId; std::map mDoubleClickActions; bool mJumpToAddedRecord; bool mUnselectAfterJump; From 86dc5a2c6702c28b9d3a89c2655db115b4bfe92e Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 19:27:42 +0300 Subject: [PATCH 28/31] Add Edit 'ID' action for nested tables --- apps/opencs/view/world/dialoguesubview.cpp | 5 ++++ apps/opencs/view/world/nestedtable.cpp | 30 ++++++++++++++++++---- apps/opencs/view/world/nestedtable.hpp | 8 ++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 283cdfa58a..4a463ecaa5 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -565,6 +565,11 @@ void CSVWorld::EditWidget::remake(int row) tablesLayout->addWidget(label); tablesLayout->addWidget(table); + + connect(table, + SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); } else if (!(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) { diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 92df59a5fc..0876b2ce78 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -1,15 +1,18 @@ #include "nestedtable.hpp" -#include "../../model/world/nestedtableproxymodel.hpp" -#include "../../model/world/universalid.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/commanddispatcher.hpp" -#include "util.hpp" #include #include #include #include +#include "../../model/world/nestedtableproxymodel.hpp" +#include "../../model/world/universalid.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/commanddispatcher.hpp" + +#include "tableeditidaction.hpp" +#include "util.hpp" + CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, @@ -55,6 +58,9 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, connect(mRemoveRowAction, SIGNAL(triggered()), this, SLOT(removeRowActionTriggered())); + + mEditIdAction = new TableEditIdAction(*this, this); + connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editCell())); } std::vector CSVWorld::NestedTable::getDraggedRecords() const @@ -69,6 +75,15 @@ void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) QMenu menu(this); + int currentRow = rowAt(event->y()); + int currentColumn = columnAt(event->x()); + if (mEditIdAction->isValidIdCell(currentRow, currentColumn)) + { + mEditIdAction->setCell(currentRow, currentColumn); + menu.addAction(mEditIdAction); + menu.addSeparator(); + } + if (selectionModel()->selectedRows().size() == 1) menu.addAction(mRemoveRowAction); @@ -92,3 +107,8 @@ void CSVWorld::NestedTable::addNewRowActionTriggered() selectionModel()->selectedRows().size(), mModel->getParentColumn())); } + +void CSVWorld::NestedTable::editCell() +{ + emit editRequest(mEditIdAction->getCurrentId(), ""); +} diff --git a/apps/opencs/view/world/nestedtable.hpp b/apps/opencs/view/world/nestedtable.hpp index 1129204011..ba8b6c0e32 100644 --- a/apps/opencs/view/world/nestedtable.hpp +++ b/apps/opencs/view/world/nestedtable.hpp @@ -22,12 +22,15 @@ namespace CSMDoc namespace CSVWorld { + class TableEditIdAction; + class NestedTable : public DragRecordTable { Q_OBJECT QAction *mAddNewRowAction; QAction *mRemoveRowAction; + TableEditIdAction *mEditIdAction; CSMWorld::NestedTableProxyModel* mModel; CSMWorld::CommandDispatcher *mDispatcher; @@ -46,6 +49,11 @@ namespace CSVWorld void removeRowActionTriggered(); void addNewRowActionTriggered(); + + void editCell(); + + signals: + void editRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; } From 30fd711a4799310dcdfd031c863c956d27b8f9ce Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 19:39:53 +0300 Subject: [PATCH 29/31] TableEditIdAction: prevent creation of a UniversalId with Type_None --- apps/opencs/view/world/tableeditidaction.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/world/tableeditidaction.cpp b/apps/opencs/view/world/tableeditidaction.cpp index 7ce726e412..4dfc537cc8 100644 --- a/apps/opencs/view/world/tableeditidaction.cpp +++ b/apps/opencs/view/world/tableeditidaction.cpp @@ -25,9 +25,13 @@ CSVWorld::TableEditIdAction::TableEditIdAction(const QTableView &table, QWidget void CSVWorld::TableEditIdAction::setCell(int row, int column) { CellData data = getCellData(row, column); - mCurrentId = CSMWorld::UniversalId(CSMWorld::TableMimeData::convertEnums(data.first), - data.second.toUtf8().constData()); - setText("Edit '" + data.second + "'"); + CSMWorld::UniversalId::Type idType = CSMWorld::TableMimeData::convertEnums(data.first); + + if (idType != CSMWorld::UniversalId::Type_None) + { + mCurrentId = CSMWorld::UniversalId(idType, data.second.toUtf8().constData()); + setText("Edit '" + data.second + "'"); + } } CSMWorld::UniversalId CSVWorld::TableEditIdAction::getCurrentId() const @@ -38,5 +42,8 @@ CSMWorld::UniversalId CSVWorld::TableEditIdAction::getCurrentId() const bool CSVWorld::TableEditIdAction::isValidIdCell(int row, int column) const { CellData data = getCellData(row, column); - return CSMWorld::ColumnBase::isId(data.first) && !data.second.isEmpty(); + CSMWorld::UniversalId::Type idType = CSMWorld::TableMimeData::convertEnums(data.first); + return CSMWorld::ColumnBase::isId(data.first) && + idType != CSMWorld::UniversalId::Type_None && + !data.second.isEmpty(); } From c1b06538dc26cc915276f154d087abc1c1549b9e Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 19:55:48 +0300 Subject: [PATCH 30/31] Add Edit 'ID' action for nested fields --- apps/opencs/view/world/dialoguesubview.cpp | 38 ++++++++++++++-------- apps/opencs/view/world/dialoguesubview.hpp | 3 ++ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 4a463ecaa5..ed50b81cd7 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -428,6 +428,28 @@ void CSVWorld::IdContextMenu::editIdRequest() =============================================================EditWidget===================================================== */ +void CSVWorld::EditWidget::createEditorContextMenu(QWidget *editor, + CSMWorld::ColumnBase::Display display, + int currentRow) const +{ + Q_ASSERT(editor != NULL); + + if (CSMWorld::ColumnBase::isId(display) && + CSMWorld::TableMimeData::convertEnums(display) != CSMWorld::UniversalId::Type_None) + { + int idColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + QString id = mTable->data(mTable->index(currentRow, idColumn)).toString(); + + IdContextMenu *menu = new IdContextMenu(editor, display); + // Current ID is already opened, so no need to create Edit 'ID' action for it + menu->excludeId(id.toUtf8().constData()); + connect(menu, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); + } +} + CSVWorld::EditWidget::~EditWidget() { for (unsigned i = 0; i < mNestedModels.size(); ++i) @@ -604,19 +626,7 @@ void CSVWorld::EditWidget::remake(int row) label->setEnabled(false); } - if (CSMWorld::ColumnBase::isId(display)) - { - int idColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_Id); - QString id = mTable->data(mTable->index(row, idColumn)).toString(); - - IdContextMenu *menu = new IdContextMenu(editor, display); - // Current ID is already opened, so no need to create Edit 'ID' action for it - menu->excludeId(id.toUtf8().constData()); - connect(menu, - SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), - this, - SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); - } + createEditorContextMenu(editor, display, row); } } else @@ -668,6 +678,8 @@ void CSVWorld::EditWidget::remake(int row) editor->setEnabled(false); label->setEnabled(false); } + + createEditorContextMenu(editor, display, row); } } mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i))); diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 91be5588f4..d82936e459 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -195,6 +195,9 @@ namespace CSVWorld CSMDoc::Document& mDocument; std::vector mNestedModels; //Plain, raw C pointers, deleted in the dtor + void createEditorContextMenu(QWidget *editor, + CSMWorld::ColumnBase::Display display, + int currentRow) const; public: EditWidget (QWidget *parent, int row, CSMWorld::IdTable* table, From c90841d64952cc245c1e8f10d7e61fbef78d01b1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 4 Jul 2015 21:00:01 +0200 Subject: [PATCH 31/31] Fix head tracking for NPCs --- apps/openmw/mwrender/npcanimation.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index becfd8f6ea..6252d392bf 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -850,7 +850,6 @@ void NpcAnimation::addControllers() Animation::addControllers(); mFirstPersonNeckController = NULL; - mHeadController = NULL; WeaponAnimation::deleteControllers(); if (mViewMode == VM_FirstPerson)