From f267497c0316f1163c4202451babf768274a6440 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 5 Jan 2015 18:52:37 +0100 Subject: [PATCH 01/10] Allow separate summoned creature instances for each spell ID (Fixes #2194) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwgui/spellicons.cpp | 2 +- apps/openmw/mwgui/spellicons.hpp | 2 +- apps/openmw/mwmechanics/activespells.cpp | 18 +- apps/openmw/mwmechanics/activespells.hpp | 3 + apps/openmw/mwmechanics/actors.cpp | 159 +----------------- apps/openmw/mwmechanics/creaturestats.cpp | 2 +- apps/openmw/mwmechanics/creaturestats.hpp | 11 +- apps/openmw/mwmechanics/magiceffects.hpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 1 + apps/openmw/mwmechanics/spells.cpp | 2 +- apps/openmw/mwmechanics/summoning.cpp | 195 ++++++++++++++++++++++ apps/openmw/mwmechanics/summoning.hpp | 35 ++++ apps/openmw/mwworld/inventorystore.cpp | 49 +++++- apps/openmw/mwworld/inventorystore.hpp | 3 + components/esm/creaturestats.cpp | 8 +- components/esm/creaturestats.hpp | 2 +- 17 files changed, 329 insertions(+), 167 deletions(-) create mode 100644 apps/openmw/mwmechanics/summoning.cpp create mode 100644 apps/openmw/mwmechanics/summoning.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c645e1a0a..97ab13012 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -76,7 +76,7 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting - disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor + disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 8ea9cfd7f..d23f1a235 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -25,7 +25,7 @@ namespace MWGui { void EffectSourceVisitor::visit (MWMechanics::EffectKey key, - const std::string& sourceName, int casterActorId, + const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime) { MagicEffectInfo newEffectSource; diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index e9d9967ea..5099fc4d6 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -47,7 +47,7 @@ namespace MWGui virtual ~EffectSourceVisitor() {} virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, int casterActorId, + const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1); }; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 717a63be8..6e15449e1 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -195,7 +195,7 @@ namespace MWMechanics float magnitude = effectIt->mMagnitude; if (magnitude) - visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), name, it->second.mCasterActorId, magnitude, remainingTime, effectIt->mDuration); + visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), name, it->first, it->second.mCasterActorId, magnitude, remainingTime, effectIt->mDuration); } } } @@ -229,6 +229,22 @@ namespace MWMechanics mSpellsChanged = true; } + void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId) + { + for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + for (std::vector::iterator effectIt = it->second.mEffects.begin(); + effectIt != it->second.mEffects.end();) + { + if (effectIt->mEffectId == effectId && it->first == sourceId) + effectIt = it->second.mEffects.erase(effectIt); + else + ++effectIt; + } + } + mSpellsChanged = true; + } + void ActiveSpells::purge(int casterActorId) { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 9c1a5e613..4f9d15d8c 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -82,6 +82,9 @@ namespace MWMechanics /// Remove all active effects with this effect id void purgeEffect (short effectId); + /// Remove all active effects with this effect id and source id + void purgeEffect (short effectId, const std::string& sourceId); + /// Remove all active effects, if roll succeeds (for each effect) void purgeAll (float chance); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 1055e0f4a..c0fc80692 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -36,6 +36,7 @@ #include "aipursue.hpp" #include "actor.hpp" +#include "summoning.hpp" namespace { @@ -105,7 +106,7 @@ public: , mCommanded(false){} virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, int casterActorId, + const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -165,30 +166,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float } } -void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId) -{ - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); - if (!ptr.isEmpty()) - { - // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation - // plays though, which is a rather lame exploit in vanilla. - MWBase::Environment::get().getWorld()->deleteObject(ptr); - - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_End"); - if (fx) - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); - } - else - { - // We didn't find the creature. It's probably in an inactive cell. - // Add to graveyard so we can delete it when the cell becomes active. - std::vector& graveyard = casterStats.getSummonedCreatureGraveyard(); - graveyard.push_back(creatureActorId); - } -} - } namespace MWMechanics @@ -203,7 +180,7 @@ namespace MWMechanics : mCreature(trappedCreature) {} virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, int casterActorId, + const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) { if (key.mId != ESM::MagicEffect::Soultrap) @@ -782,131 +759,11 @@ namespace MWMechanics } } - // Update summon effects - static std::map summonMap; - if (summonMap.empty()) - { - summonMap[ESM::MagicEffect::SummonAncestralGhost] = "sMagicAncestralGhostID"; - summonMap[ESM::MagicEffect::SummonBonelord] = "sMagicBonelordID"; - summonMap[ESM::MagicEffect::SummonBonewalker] = "sMagicLeastBonewalkerID"; - summonMap[ESM::MagicEffect::SummonCenturionSphere] = "sMagicCenturionSphereID"; - summonMap[ESM::MagicEffect::SummonClannfear] = "sMagicClannfearID"; - summonMap[ESM::MagicEffect::SummonDaedroth] = "sMagicDaedrothID"; - summonMap[ESM::MagicEffect::SummonDremora] = "sMagicDremoraID"; - summonMap[ESM::MagicEffect::SummonFabricant] = "sMagicFabricantID"; - summonMap[ESM::MagicEffect::SummonFlameAtronach] = "sMagicFlameAtronachID"; - summonMap[ESM::MagicEffect::SummonFrostAtronach] = "sMagicFrostAtronachID"; - summonMap[ESM::MagicEffect::SummonGoldenSaint] = "sMagicGoldenSaintID"; - summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "sMagicGreaterBonewalkerID"; - summonMap[ESM::MagicEffect::SummonHunger] = "sMagicHungerID"; - summonMap[ESM::MagicEffect::SummonScamp] = "sMagicScampID"; - summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "sMagicSkeletalMinionID"; - summonMap[ESM::MagicEffect::SummonStormAtronach] = "sMagicStormAtronachID"; - summonMap[ESM::MagicEffect::SummonWingedTwilight] = "sMagicWingedTwilightID"; - summonMap[ESM::MagicEffect::SummonWolf] = "sMagicCreature01ID"; - summonMap[ESM::MagicEffect::SummonBear] = "sMagicCreature02ID"; - summonMap[ESM::MagicEffect::SummonBonewolf] = "sMagicCreature03ID"; - summonMap[ESM::MagicEffect::SummonCreature04] = "sMagicCreature04ID"; - summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID"; - } - - std::map& creatureMap = creatureStats.getSummonedCreatureMap(); - for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) - { - bool found = creatureMap.find(it->first) != creatureMap.end(); - int magnitude = creatureStats.getMagicEffects().get(it->first).getMagnitude(); - if (found != (magnitude > 0)) - { - if (magnitude > 0) - { - ESM::Position ipos = ptr.getRefData().getPosition(); - Ogre::Vector3 pos(ipos.pos); - Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); - const float distance = 50; - pos = pos + distance*rot.yAxis(); - ipos.pos[0] = pos.x; - ipos.pos[1] = pos.y; - ipos.pos[2] = pos.z; - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; - - std::string creatureID = - MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->getString(); - - if (!creatureID.empty()) - { - MWWorld::CellStore* store = ptr.getCell(); - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); - ref.getPtr().getCellRef().setPosition(ipos); - - MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); - - // Make the summoned creature follow its master and help in fights - AiFollow package(ptr.getCellRef().getRefId()); - summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); - int creatureActorId = summonedCreatureStats.getActorId(); - - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); - - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); - if (anim) - { - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_Start"); - if (fx) - anim->addEffect("meshes\\" + fx->mModel, -1, false); - } - - creatureMap.insert(std::make_pair(it->first, creatureActorId)); - } - } - else - { - // Effect has ended - std::map::iterator foundCreature = creatureMap.find(it->first); - cleanupSummonedCreature(creatureStats, foundCreature->second); - creatureMap.erase(foundCreature); - } - } - } - - for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) - { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); - if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead()) - { - // Purge the magic effect so a new creature can be summoned if desired - creatureStats.getActiveSpells().purgeEffect(it->first); - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first); - - cleanupSummonedCreature(creatureStats, it->second); - creatureMap.erase(it++); - } - else - ++it; - } - - std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); - for (std::vector::iterator it = graveyard.begin(); it != graveyard.end(); ) - { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(*it); - if (!ptr.isEmpty()) - { - it = graveyard.erase(it); - - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_End"); - if (fx) - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); - - MWBase::Environment::get().getWorld()->deleteObject(ptr); - } - else - ++it; - } + UpdateSummonedCreatures updateSummonedCreatures(ptr); + creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures); + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures); + updateSummonedCreatures.finish(); } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index ac6f88d44..c61cc9697 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -638,7 +638,7 @@ namespace MWMechanics mDeathAnimation = index; } - std::map& CreatureStats::getSummonedCreatureMap() + std::map& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 9a08b58c9..145eb8a5b 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -67,8 +67,11 @@ namespace MWMechanics // The index of the death animation that was played unsigned char mDeathAnimation; - // - std::map mSummonedCreatures; + public: + typedef std::pair SummonKey; // + private: + std::map mSummonedCreatures; // + // Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet. // This may be necessary when the creature is in an inactive cell. std::vector mSummonGraveyard; @@ -216,8 +219,8 @@ namespace MWMechanics void setBlock(bool value); bool getBlock() const; - std::map& getSummonedCreatureMap(); - std::vector& getSummonedCreatureGraveyard(); + std::map& getSummonedCreatureMap(); // + std::vector& getSummonedCreatureGraveyard(); // ActorIds enum Flag { diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 88d8d988f..c384d0857 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -73,7 +73,7 @@ namespace MWMechanics struct EffectSourceVisitor { virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, int casterActorId, + const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) = 0; }; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 58ccd389a..9cfcdda18 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -21,6 +21,7 @@ #include "magiceffects.hpp" #include "npcstats.hpp" +#include "summoning.hpp" namespace { diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 5953be523..1f8ada06d 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -249,7 +249,7 @@ namespace MWMechanics random = it->second.at(i); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; - visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, -1, magnitude); + visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, spell->mId, -1, magnitude); } } } diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp new file mode 100644 index 000000000..356cb422f --- /dev/null +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -0,0 +1,195 @@ +#include "summoning.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/esmstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/inventorystore.hpp" + +#include "../mwrender/animation.hpp" + +#include "creaturestats.hpp" +#include "aifollow.hpp" + + +namespace MWMechanics +{ + + void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); + if (!ptr.isEmpty()) + { + // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation + // plays though, which is a rather lame exploit in vanilla. + MWBase::Environment::get().getWorld()->deleteObject(ptr); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); + } + else + { + // We didn't find the creature. It's probably in an inactive cell. + // Add to graveyard so we can delete it when the cell becomes active. + std::vector& graveyard = casterStats.getSummonedCreatureGraveyard(); + graveyard.push_back(creatureActorId); + } + } + + UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor) + : mActor(actor) + { + + } + + void UpdateSummonedCreatures::visit(EffectKey key, const std::string &sourceName, const std::string &sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime) + { + if (key.mId >= ESM::MagicEffect::SummonScamp + && key.mId <= ESM::MagicEffect::SummonStormAtronach && magnitude > 0) + { + mActiveEffects.insert(std::make_pair(key.mId, sourceId)); + } + } + + void UpdateSummonedCreatures::finish() + { + static std::map summonMap; + if (summonMap.empty()) + { + summonMap[ESM::MagicEffect::SummonAncestralGhost] = "sMagicAncestralGhostID"; + summonMap[ESM::MagicEffect::SummonBonelord] = "sMagicBonelordID"; + summonMap[ESM::MagicEffect::SummonBonewalker] = "sMagicLeastBonewalkerID"; + summonMap[ESM::MagicEffect::SummonCenturionSphere] = "sMagicCenturionSphereID"; + summonMap[ESM::MagicEffect::SummonClannfear] = "sMagicClannfearID"; + summonMap[ESM::MagicEffect::SummonDaedroth] = "sMagicDaedrothID"; + summonMap[ESM::MagicEffect::SummonDremora] = "sMagicDremoraID"; + summonMap[ESM::MagicEffect::SummonFabricant] = "sMagicFabricantID"; + summonMap[ESM::MagicEffect::SummonFlameAtronach] = "sMagicFlameAtronachID"; + summonMap[ESM::MagicEffect::SummonFrostAtronach] = "sMagicFrostAtronachID"; + summonMap[ESM::MagicEffect::SummonGoldenSaint] = "sMagicGoldenSaintID"; + summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "sMagicGreaterBonewalkerID"; + summonMap[ESM::MagicEffect::SummonHunger] = "sMagicHungerID"; + summonMap[ESM::MagicEffect::SummonScamp] = "sMagicScampID"; + summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "sMagicSkeletalMinionID"; + summonMap[ESM::MagicEffect::SummonStormAtronach] = "sMagicStormAtronachID"; + summonMap[ESM::MagicEffect::SummonWingedTwilight] = "sMagicWingedTwilightID"; + summonMap[ESM::MagicEffect::SummonWolf] = "sMagicCreature01ID"; + summonMap[ESM::MagicEffect::SummonBear] = "sMagicCreature02ID"; + summonMap[ESM::MagicEffect::SummonBonewolf] = "sMagicCreature03ID"; + summonMap[ESM::MagicEffect::SummonCreature04] = "sMagicCreature04ID"; + summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID"; + } + + MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); + + // Update summon effects + std::map& creatureMap = creatureStats.getSummonedCreatureMap(); + for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) + { + bool found = mActiveEffects.find(it->first) != mActiveEffects.end(); + if (!found) + { + // Effect has ended + cleanupSummonedCreature(creatureStats, it->second); + creatureMap.erase(it++); + continue; + } + ++it; + } + + for (std::set >::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it) + { + bool found = creatureMap.find(std::make_pair(it->first, it->second)) != creatureMap.end(); + if (!found) + { + ESM::Position ipos = mActor.getRefData().getPosition(); + Ogre::Vector3 pos(ipos.pos); + Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); + const float distance = 50; + pos = pos + distance*rot.yAxis(); + ipos.pos[0] = pos.x; + ipos.pos[1] = pos.y; + ipos.pos[2] = pos.z; + ipos.rot[0] = 0; + ipos.rot[1] = 0; + ipos.rot[2] = 0; + + const std::string& creatureGmst = summonMap[it->first]; + std::string creatureID = + MWBase::Environment::get().getWorld()->getStore().get().find(creatureGmst)->getString(); + + if (!creatureID.empty()) + { + MWWorld::CellStore* store = mActor.getCell(); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); + ref.getPtr().getCellRef().setPosition(ipos); + + MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); + + // Make the summoned creature follow its master and help in fights + AiFollow package(mActor.getCellRef().getRefId()); + summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); + int creatureActorId = summonedCreatureStats.getActorId(); + + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); + if (anim) + { + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_Start"); + if (fx) + anim->addEffect("meshes\\" + fx->mModel, -1, false); + } + + creatureMap.insert(std::make_pair(*it, creatureActorId)); + } + } + } + + for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); + if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead()) + { + // Purge the magic effect so a new creature can be summoned if desired + creatureStats.getActiveSpells().purgeEffect(it->first.first, it->first.second); + if (mActor.getClass().hasInventoryStore(ptr)) + mActor.getClass().getInventoryStore(mActor).purgeEffect(it->first.first, it->first.second); + + cleanupSummonedCreature(creatureStats, it->second); + creatureMap.erase(it++); + } + else + ++it; + } + + std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); + for (std::vector::iterator it = graveyard.begin(); it != graveyard.end(); ) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(*it); + if (!ptr.isEmpty()) + { + it = graveyard.erase(it); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); + + MWBase::Environment::get().getWorld()->deleteObject(ptr); + } + else + ++it; + } + } + +} diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp new file mode 100644 index 000000000..b8fe37783 --- /dev/null +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -0,0 +1,35 @@ +#ifndef OPENMW_MECHANICS_SUMMONING_H +#define OPENMW_MECHANICS_SUMMONING_H + +#include + +#include "magiceffects.hpp" +#include "../mwworld/ptr.hpp" + +namespace MWMechanics +{ + + class CreatureStats; + + struct UpdateSummonedCreatures : public EffectSourceVisitor + { + UpdateSummonedCreatures(const MWWorld::Ptr& actor); + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1); + + /// To call after all effect sources have been visited + void finish(); + + private: + MWWorld::Ptr mActor; + + std::set > mActiveEffects; + }; + + void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId); + +} + +#endif diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 9c329ce72..445b42d8d 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -583,7 +583,8 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i]; float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; magnitude *= params.mMultiplier; - visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), -1, magnitude); + if (magnitude > 0) + visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude); ++i; } @@ -639,6 +640,52 @@ void MWWorld::InventoryStore::purgeEffect(short effectId) mMagicEffects.remove(MWMechanics::EffectKey(effectId)); } +void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId) +{ + TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId); + if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end()) + return; + + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) + { + if (*iter==end()) + continue; + + if ((*iter)->getClass().getId(**iter) != sourceId) + continue; + + std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); + + if (!enchantmentId.empty()) + { + const ESM::Enchantment& enchantment = + *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); + + if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) + continue; + + std::vector& params = effectMagnitudeIt->second; + + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) + { + if (effectIt->mEffectID != effectId) + continue; + + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; + magnitude *= params[i].mMultiplier; + + if (magnitude) + mMagicEffects.add (*effectIt, -magnitude); + + params[i].mMultiplier = 0; + break; + } + } + } +} + void MWWorld::InventoryStore::clear() { mSlots.clear(); diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 48742b557..9fd18c54b 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -203,6 +203,9 @@ namespace MWWorld void purgeEffect (short effectId); ///< Remove a magic effect + void purgeEffect (short effectId, const std::string& sourceId); + ///< Remove a magic effect + virtual void clear(); ///< Empty container. diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index cc76ef0df..1fdce703c 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -94,9 +94,10 @@ void ESM::CreatureStats::load (ESMReader &esm) { int magicEffect; esm.getHT(magicEffect); + std::string source = esm.getHNOString("SOUR"); int actorId; esm.getHNT (actorId, "ACID"); - mSummonedCreatureMap[magicEffect] = actorId; + mSummonedCreatureMap[std::make_pair(magicEffect, source)] = actorId; } while (esm.isNextSub("GRAV")) @@ -204,9 +205,10 @@ void ESM::CreatureStats::save (ESMWriter &esm) const mAiSequence.save(esm); mMagicEffects.save(esm); - for (std::map::const_iterator it = mSummonedCreatureMap.begin(); it != mSummonedCreatureMap.end(); ++it) + for (std::map, int>::const_iterator it = mSummonedCreatureMap.begin(); it != mSummonedCreatureMap.end(); ++it) { - esm.writeHNT ("SUMM", it->first); + esm.writeHNT ("SUMM", it->first.first); + esm.writeHNString ("SOUR", it->first.second); esm.writeHNT ("ACID", it->second); } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 7946d0e45..680577f93 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -32,7 +32,7 @@ namespace ESM bool mHasAiSettings; StatState mAiSettings[4]; - std::map mSummonedCreatureMap; + std::map, int> mSummonedCreatureMap; std::vector mSummonGraveyard; ESM::TimeStamp mTradeTime; From 992b87ea441de3be8c673f300532f902330c0dda Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 6 Jan 2015 16:08:23 +0100 Subject: [PATCH 02/10] Reset existing summons when the spell is re-casted (Fixes #2135) --- apps/openmw/mwmechanics/spellcasting.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 9cfcdda18..b7c7e00a1 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -471,6 +471,20 @@ namespace MWMechanics else applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude); + // Re-casting a summon effect will remove the creature from previous castings of that effect. + if (effectIt->mEffectID >= ESM::MagicEffect::SummonScamp + && effectIt->mEffectID <= ESM::MagicEffect::SummonStormAtronach + && !target.isEmpty() && target.getClass().isActor()) + { + CreatureStats& targetStats = target.getClass().getCreatureStats(target); + std::map::iterator found = targetStats.getSummonedCreatureMap().find(std::make_pair(effectIt->mEffectID, mId)); + if (found != targetStats.getSummonedCreatureMap().end()) + { + cleanupSummonedCreature(targetStats, found->second); + targetStats.getSummonedCreatureMap().erase(found); + } + } + // HACK: Damage attribute/skill actually has a duration, even though the actual effect is instant and permanent. // This was probably just done to have the effect visible in the magic menu for a while // to notify the player they've been damaged? From e19ab77d00301108d06307142e201b4f6edbd813 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 6 Jan 2015 19:29:33 +0100 Subject: [PATCH 03/10] Store camera first person state in savegame (Fixes #2255) --- apps/openmw/mwstate/statemanagerimp.cpp | 3 +++ apps/openmw/mwworld/worldimp.cpp | 16 ++++++++++++++-- components/esm/defs.hpp | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index f77a90d8e..30dba2aaa 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -355,6 +355,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl case ESM::REC_ENAB: case ESM::REC_LEVC: case ESM::REC_LEVI: + case ESM::REC_CAM_: MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); break; @@ -406,6 +407,8 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl // Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false); + // Vanilla MW will restart startup scripts when a save game is loaded. This is unintuive, + // but some mods may be using it as a reload detector. MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); // Do not trigger erroneous cellChanged events diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1c9b8b996..a939a6db9 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -201,6 +201,7 @@ namespace MWWorld setupPlayer(); renderPlayer(); + mRendering->resetCamera(); MWBase::Environment::get().getWindowManager()->updatePlayer(); @@ -304,7 +305,8 @@ namespace MWWorld +1 // player record +1 // weather record +1 // actorId counter - +1; // levitation/teleport enabled state + +1 // levitation/teleport enabled state + +1; // camera } void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const @@ -333,6 +335,11 @@ namespace MWWorld writer.writeHNT("LEVT", mLevitationEnabled); writer.endRecord(ESM::REC_ENAB); progress.increaseProgress(); + + writer.startRecord(ESM::REC_CAM_); + writer.writeHNT("FIRS", isFirstPerson()); + writer.endRecord(ESM::REC_CAM_); + progress.increaseProgress(); } void World::readRecord (ESM::ESMReader& reader, int32_t type, @@ -347,6 +354,12 @@ namespace MWWorld reader.getHNT(mTeleportEnabled, "TELE"); reader.getHNT(mLevitationEnabled, "LEVT"); return; + case ESM::REC_CAM_: + bool firstperson; + reader.getHNT(firstperson, "FIRS"); + if (firstperson != isFirstPerson()) + togglePOV(); + break; default: if (!mStore.readRecord (reader, type) && !mGlobalVariables.readRecord (reader, type) && @@ -2073,7 +2086,6 @@ namespace MWWorld MWBase::Environment::get().getMechanicsManager()->add(mPlayer->getPlayer()); mPhysics->addActor(mPlayer->getPlayer()); - mRendering->resetCamera(); } int World::canRest () diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index d5ba919b0..fe45d6914 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -114,6 +114,7 @@ enum RecNameInts REC_DCOU = FourCC<'D','C','O','U'>::value, REC_MARK = FourCC<'M','A','R','K'>::value, REC_ENAB = FourCC<'E','N','A','B'>::value, + REC_CAM_ = FourCC<'C','A','M','_'>::value, // format 1 REC_FILT = 0x544C4946, From 4d9100091d3356dfadef0b55e83970da689ba406 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 6 Jan 2015 22:19:04 +0100 Subject: [PATCH 04/10] Reduce default pathing arrival tolerance to 32 units (Fixes #1605) --- apps/openmw/mwmechanics/aiwander.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 4 ++-- apps/openmw/mwmechanics/pathfinding.hpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 2df3762ab..94c4542f8 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -202,7 +202,7 @@ namespace MWMechanics // Are we there yet? bool& chooseAction = storage.mChooseAction; if(walking && - storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) + storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2], 64.f)) { stopWalking(actor, storage); moveNow = false; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index f1279c415..f59167df7 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -297,13 +297,13 @@ namespace MWMechanics return false; } - bool PathFinder::checkPathCompleted(float x, float y, float z) + bool PathFinder::checkPathCompleted(float x, float y, float z, float tolerance) { if(mPath.empty()) return true; ESM::Pathgrid::Point nextPoint = *mPath.begin(); - if(sqrDistanceZCorrected(nextPoint, x, y, z) < 64*64) + if(sqrDistanceZCorrected(nextPoint, x, y, z) < tolerance*tolerance) { mPath.pop_front(); if(mPath.empty()) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 482808dac..61008577c 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -39,8 +39,8 @@ namespace MWMechanics void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, const MWWorld::CellStore* cell, bool allowShortcuts = true); - bool checkPathCompleted(float x, float y, float z); - ///< \Returns true if the last point of the path has been reached. + bool checkPathCompleted(float x, float y, float z, float tolerance=32.f); + ///< \Returns true if we are within \a tolerance units of the last path point. bool checkWaypoint(float x, float y, float z); ///< \Returns true if a way point was reached From a17252eab3415d2b1d987ccc8c3a3ef3c1481b32 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 6 Jan 2015 23:23:58 +0100 Subject: [PATCH 05/10] Remove unused checkWaypoint function It was an almost exact copy of the checkPathCompleted function anyway. --- apps/openmw/mwmechanics/aicombat.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 15 --------------- apps/openmw/mwmechanics/pathfinding.hpp | 3 --- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index a23634ea3..5d0470554 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -580,7 +580,7 @@ namespace MWMechanics buildNewPath(actor, target); //may fail to build a path, check before use //delete visited path node - mPathFinder.checkWaypoint(pos.pos[0],pos.pos[1],pos.pos[2]); + mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); // This works on the borders between the path grid and areas with no waypoints. if(inLOS && mPathFinder.getPath().size() > 1) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index f59167df7..0634725a8 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -282,21 +282,6 @@ namespace MWMechanics return Ogre::Math::ATan2(directionX,directionY).valueDegrees(); } - bool PathFinder::checkWaypoint(float x, float y, float z) - { - if(mPath.empty()) - return true; - - ESM::Pathgrid::Point nextPoint = *mPath.begin(); - if(sqrDistanceZCorrected(nextPoint, x, y, z) < 64*64) - { - mPath.pop_front(); - if(mPath.empty()) mIsPathConstructed = false; - return true; - } - return false; - } - bool PathFinder::checkPathCompleted(float x, float y, float z, float tolerance) { if(mPath.empty()) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 61008577c..7ba2d22ba 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -42,9 +42,6 @@ namespace MWMechanics bool checkPathCompleted(float x, float y, float z, float tolerance=32.f); ///< \Returns true if we are within \a tolerance units of the last path point. - bool checkWaypoint(float x, float y, float z); - ///< \Returns true if a way point was reached - float getZAngleToNext(float x, float y) const; bool isPathConstructed() const From d02e075bab885d805e8c6d7b994da75c69f69154 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 6 Jan 2015 23:54:53 +0100 Subject: [PATCH 06/10] Add setting for exterior cell grid size (Fixes #1537) --- apps/openmw/mwworld/scene.cpp | 16 +++++++++------- files/settings-default.cfg | 3 +++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 02c9db9ea..efe88c406 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -296,15 +296,17 @@ namespace MWWorld std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); + const int halfGridSize = Settings::Manager::getInt("exterior grid size", "Cells")/2; + CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) { if ((*active)->getCell()->isExterior()) { - if (std::abs (X-(*active)->getCell()->getGridX())<=1 && - std::abs (Y-(*active)->getCell()->getGridY())<=1) + if (std::abs (X-(*active)->getCell()->getGridX())<=halfGridSize && + std::abs (Y-(*active)->getCell()->getGridY())<=halfGridSize) { - // keep cells within the new 3x3 grid + // keep cells within the new grid ++active; continue; } @@ -314,9 +316,9 @@ namespace MWWorld int refsToLoad = 0; // get the number of refs to load - for (int x=X-1; x<=X+1; ++x) + for (int x=X-halfGridSize; x<=X+halfGridSize; ++x) { - for (int y=Y-1; y<=Y+1; ++y) + for (int y=Y-halfGridSize; y<=Y+halfGridSize; ++y) { CellStoreCollection::iterator iter = mActiveCells.begin(); @@ -339,9 +341,9 @@ namespace MWWorld loadingListener->setProgressRange(refsToLoad); // Load cells - for (int x=X-1; x<=X+1; ++x) + for (int x=X-halfGridSize; x<=X+halfGridSize; ++x) { - for (int y=Y-1; y<=Y+1; ++y) + for (int y=Y-halfGridSize; y<=Y+halfGridSize; ++y) { CellStoreCollection::iterator iter = mActiveCells.begin(); diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 6ec9b03e9..f91299a1f 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -123,6 +123,9 @@ local map resolution = 256 local map widget size = 512 local map hud widget size = 256 +[Cells] +exterior grid size = 3 + [Viewing distance] # Limit the rendering distance of small objects limit small object distance = false From dc5ed5b8613b9074ecc6a25e4a41b1825909abb6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 7 Jan 2015 01:11:39 +0100 Subject: [PATCH 07/10] Remove weather particles underwater (Fixes #2010) --- apps/openmw/mwrender/sky.cpp | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 761d3fd96..0b9dc091e 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -431,7 +432,10 @@ void SkyManager::updateRain(float dt) Ogre::Vector3 pos = it->first->getPosition(); pos.z -= mRainSpeed * dt; it->first->setPosition(pos); - if (pos.z < -minHeight) + if (pos.z < -minHeight + // Here we might want to add a "splash" effect later + || MWBase::Environment::get().getWorld()->isUnderwater( + MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), it->first->_getDerivedPosition())) { it->second.setNull(); mSceneMgr->destroySceneNode(it->first); @@ -458,6 +462,12 @@ void SkyManager::updateRain(float dt) // Create a separate node to control the offset, since a node with setInheritOrientation(false) will still // consider the orientation of the parent node for its position, just not for its orientation float startHeight = 700; + Ogre::Vector3 worldPos = mParticleNode->_getDerivedPosition(); + worldPos += Ogre::Vector3(xOffs, yOffs, startHeight); + if (MWBase::Environment::get().getWorld()->isUnderwater( + MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), worldPos)) + return; + Ogre::SceneNode* offsetNode = mParticleNode->createChildSceneNode(Ogre::Vector3(xOffs,yOffs,startHeight)); // Spawn a new rain object for each instance. @@ -490,6 +500,30 @@ void SkyManager::update(float duration) for (unsigned int i=0; imControllers.size(); ++i) mParticle->mControllers[i].update(); + for (unsigned int i=0; imParticles.size(); ++i) + { + Ogre::ParticleSystem* psys = mParticle->mParticles[i]; + Ogre::ParticleIterator pi = psys->_getIterator(); + while (!pi.end()) + { + Ogre::Particle *p = pi.getNext(); + #if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + Ogre::Vector3 pos = p->mPosition; + Ogre::Real& timeToLive = p->mTimeToLive; + #else + Ogre::Vector3 pos = p->position; + Ogre::Real& timeToLive = p->timeToLive; + #endif + + if (psys->getKeepParticlesInLocalSpace() && psys->getParentNode()) + pos = psys->getParentNode()->convertLocalToWorldPosition(pos); + + if (MWBase::Environment::get().getWorld()->isUnderwater( + MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), pos)) + timeToLive = 0; + } + } + if (mIsStorm) mParticleNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection)); } From 157438460b1500ec2277d4f809b3fc383cba36d0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 7 Jan 2015 01:30:59 +0100 Subject: [PATCH 08/10] Fix being able to activate objects when paralyzed --- apps/openmw/engine.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 24e1388d0..c48f509b5 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -514,7 +514,8 @@ void OMW::Engine::activate() return; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (player.getClass().getCreatureStats(player).getKnockedDown()) + if (player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0 + || player.getClass().getCreatureStats(player).getKnockedDown()) return; MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject(); From ef1b0a191b0370a711377093b84ccbbbe676955b Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 7 Jan 2015 03:48:16 +0100 Subject: [PATCH 09/10] Revert "Enchanting: fix inverted self-enchant success chance" It wasn't inverted to begin with. The author of this commit is an idiot. --- apps/openmw/mwmechanics/enchanting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 8c85e5eef..08df95fd9 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -65,7 +65,7 @@ namespace MWMechanics if(mSelfEnchanting) { - if(std::rand()/static_cast (RAND_MAX)*100 < getEnchantChance()) + if(getEnchantChance() (RAND_MAX)*100) return false; mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); From 13c5bd5cc27cde385285776d48cd9e1aff7b42eb Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 7 Jan 2015 04:28:56 +0100 Subject: [PATCH 10/10] Enchanting: fix skill-based cast cost bonus being applied twice --- apps/openmw/mwgui/enchantingdialog.cpp | 2 +- apps/openmw/mwgui/spellmodel.cpp | 4 +--- apps/openmw/mwmechanics/enchanting.cpp | 23 ++++++++++------------- apps/openmw/mwmechanics/enchanting.hpp | 3 ++- apps/openmw/mwmechanics/spellcasting.cpp | 16 +++++++++++++--- apps/openmw/mwmechanics/spellcasting.hpp | 2 ++ 6 files changed, 29 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 4744fd1a1..d2315be38 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -111,7 +111,7 @@ namespace MWGui mCharge->setCaption(boost::lexical_cast(mEnchanting.getGemCharge())); std::stringstream castCost; - castCost << mEnchanting.getCastCost(); + castCost << mEnchanting.getEffectiveCastCost(); mCastCost->setCaption(castCost.str()); mPrice->setCaption(boost::lexical_cast(mEnchanting.getEnchantPrice())); diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index ad9a913fa..4713720cd 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -103,9 +103,7 @@ namespace MWGui && item.getClass().canBeEquipped(item, mActor).first == 0) continue; - float enchantCost = enchant->mData.mCost; - int eSkill = mActor.getClass().getSkill(mActor, ESM::Skill::Enchant); - int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); + int castCost = MWMechanics::getEffectiveEnchantmentCastCost(enchant->mData.mCost, mActor); std::string cost = boost::lexical_cast(castCost); int currentCharge = int(item.getCellRef().getEnchantmentCharge()); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 08df95fd9..96afe2e2a 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -6,6 +6,7 @@ #include "creaturestats.hpp" #include "npcstats.hpp" +#include "spellcasting.hpp" namespace MWMechanics { @@ -55,7 +56,7 @@ namespace MWMechanics enchantment.mData.mCharge = getGemCharge(); enchantment.mData.mAutocalc = 0; enchantment.mData.mType = mCastStyle; - enchantment.mData.mCost = getCastCost(); + enchantment.mData.mCost = getBaseCastCost(); store.remove(mSoulGemPtr, 1, player); @@ -199,23 +200,19 @@ namespace MWMechanics } - int Enchanting::getCastCost() const + int Enchanting::getBaseCastCost() const { if (mCastStyle == ESM::Enchantment::ConstantEffect) return 0; - const float enchantCost = getEnchantPoints(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player); - int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); - - /* - * Each point of enchant skill above/under 10 subtracts/adds - * one percent of enchantment cost while minimum is 1. - */ - const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); + return getEnchantPoints(); + } - return static_cast((castCost < 1) ? 1 : castCost); + int Enchanting::getEffectiveCastCost() const + { + int baseCost = getBaseCastCost(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + return getEffectiveEnchantmentCastCost(baseCost, player); } diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 2ee5ccce4..2c05d2d2e 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -36,7 +36,8 @@ namespace MWMechanics void nextCastStyle(); //Set enchant type to next possible type (for mOldItemPtr object) int getCastStyle() const; int getEnchantPoints() const; - int getCastCost() const; + int getBaseCastCost() const; // To be saved in the enchantment's record + int getEffectiveCastCost() const; // Effective cost taking player Enchant skill into account, used for preview purposes in the UI int getEnchantPrice() const; int getMaxEnchantValue() const; int getGemCharge() const; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index b7c7e00a1..d9df1d00e 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -689,9 +689,7 @@ namespace MWMechanics // Check if there's enough charge left if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { - const float enchantCost = enchantment->mData.mCost; - int eSkill = mCaster.getClass().getSkill(mCaster, ESM::Skill::Enchant); - const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); + const int castCost = getEffectiveEnchantmentCastCost(enchantment->mData.mCost, mCaster); if (item.getCellRef().getEnchantmentCharge() == -1) item.getCellRef().setEnchantmentCharge(enchantment->mData.mCharge); @@ -915,4 +913,16 @@ namespace MWMechanics return true; } + + int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) + { + /* + * Each point of enchant skill above/under 10 subtracts/adds + * one percent of enchantment cost while minimum is 1. + */ + int eSkill = actor.getClass().getSkill(actor, ESM::Skill::Enchant); + const float result = castCost - (castCost / 100) * (eSkill - 10); + + return static_cast((result < 1) ? 1 : result); + } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 395ae043b..a79eeec6b 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -58,6 +58,8 @@ namespace MWMechanics float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL, const MagicEffects* effects = NULL); + int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); + class CastSpell { private: