From ee2b81763ea336684c1fae4e2f8a3e22e22d696d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Jun 2014 21:19:37 +0200 Subject: [PATCH] Savegame: Store AiSettings and summoned creatures CreatureStats state is now completely stored (Closes #1174) Also play VFX_Summon_Start and VFX_Summon_End visual effects. --- apps/openmw/mwbase/world.hpp | 2 + apps/openmw/mwmechanics/actors.cpp | 45 ++++++++++++++++++----- apps/openmw/mwmechanics/creaturestats.cpp | 24 ++++++++++++ apps/openmw/mwmechanics/creaturestats.hpp | 16 ++++---- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 5 +++ apps/openmw/mwworld/worldimp.hpp | 2 + components/esm/creaturestats.cpp | 40 ++++++++++++++++++++ components/esm/creaturestats.hpp | 6 +++ 9 files changed, 124 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 9fb91398da..df75ce64d1 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -515,6 +515,8 @@ namespace MWBase /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0; + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos) = 0; + virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 43857e5194..e9e9fbe28f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -20,6 +20,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwrender/animation.hpp" + #include "npcstats.hpp" #include "creaturestats.hpp" #include "movement.hpp" @@ -529,7 +531,8 @@ namespace MWMechanics for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) { - bool found = creatureStats.mSummonedCreatures.find(it->first) != creatureStats.mSummonedCreatures.end(); + std::map& creatureMap = creatureStats.getSummonedCreatureMap(); + bool found = creatureMap.find(it->first) != creatureMap.end(); int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude; if (found != (magnitude > 0)) { @@ -563,17 +566,25 @@ namespace MWMechanics summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); int creatureActorId = summonedCreatureStats.getActorId(); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + 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); + } - // TODO: VFX_SummonStart, VFX_SummonEnd - creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, creatureActorId)); + creatureMap.insert(std::make_pair(it->first, creatureActorId)); } } else { // Summon lifetime has expired. Try to delete the creature. - int actorId = creatureStats.mSummonedCreatures[it->first]; - creatureStats.mSummonedCreatures.erase(it->first); + int actorId = creatureMap[it->first]; + creatureMap.erase(it->first); MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(actorId); if (!ptr.isEmpty()) @@ -581,24 +592,38 @@ namespace MWMechanics // 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); - creatureStats.mSummonedCreatures.erase(it->first); + + 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. - creatureStats.mSummonGraveyard.push_back(actorId); + std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); + graveyard.push_back(actorId); } } } } - for (std::vector::iterator it = creatureStats.mSummonGraveyard.begin(); it != creatureStats.mSummonGraveyard.end(); ) + 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 = creatureStats.mSummonGraveyard.erase(it); + 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 diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 7396b27355..e26e7a8ba2 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -504,6 +504,13 @@ namespace MWMechanics mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); mAiSequence.writeState(state.mAiSequence); + + state.mSummonedCreatureMap = mSummonedCreatures; + state.mSummonGraveyard = mSummonGraveyard; + + state.mHasAiSettings = true; + for (int i=0; i<4; ++i) + mAiSettings[i].writeState (state.mAiSettings[i]); } void CreatureStats::readState (const ESM::CreatureStats& state) @@ -545,6 +552,13 @@ namespace MWMechanics mSpells.readState(state.mSpells); mActiveSpells.readState(state.mActiveSpells); mAiSequence.readState(state.mAiSequence); + + mSummonedCreatures = state.mSummonedCreatureMap; + mSummonGraveyard = state.mSummonGraveyard; + + if (state.mHasAiSettings) + for (int i=0; i<4; ++i) + mAiSettings[i].readState(state.mAiSettings[i]); } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) @@ -605,4 +619,14 @@ namespace MWMechanics { mDeathAnimation = index; } + + std::map& CreatureStats::getSummonedCreatureMap() + { + return mSummonedCreatures; + } + + std::vector& CreatureStats::getSummonedCreatureGraveyard() + { + return mSummonGraveyard; + } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 8b2398dfbd..d02af8fb6f 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -67,6 +67,12 @@ namespace MWMechanics // The index of the death animation that was played unsigned char mDeathAnimation; + // + 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; + protected: // These two are only set by NpcStats, but they are declared in CreatureStats to prevent using virtual methods. bool mIsWerewolf; @@ -208,6 +214,9 @@ namespace MWMechanics void setBlock(bool value); bool getBlock() const; + std::map& getSummonedCreatureMap(); + std::vector& getSummonedCreatureGraveyard(); + enum Flag { Flag_ForceRun = 1, @@ -233,13 +242,6 @@ namespace MWMechanics // TODO: Put it somewhere else? std::set mBoundItems; - // TODO: store in savegame - // TODO: encapsulate? - // - std::map mSummonedCreatures; - // Contains summoned creatures with an expired lifetime that have not been deleted yet. - std::vector mSummonGraveyard; - void writeState (ESM::CreatureStats& state) const; void readState (const ESM::CreatureStats& state); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index b2d69b79a4..297af558f7 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -210,7 +210,7 @@ public: /** * @brief Add an effect mesh attached to a bone or the insert scene node * @param model - * @param effectId An ID for this effect. Note that adding the same ID again won't add another effect. + * @param effectId An ID for this effect by which you can identify it later. If this is not wanted, set to -1. * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, * you need to remove it manually using removeEffect when the effect should end. * @param bonename Bone to attach to, or empty string to use the scene node instead diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e6049c1853..625942da77 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2704,6 +2704,11 @@ namespace MWWorld mRendering->spawnEffect(model, texture, worldPosition); } + void World::spawnEffect(const std::string &model, const std::string &textureOverride, const Vector3 &worldPos) + { + mRendering->spawnEffect(model, textureOverride, worldPos); + } + void World::explodeSpell(const Vector3 &origin, const ESM::EffectList &effects, const Ptr &caster, const std::string& id, const std::string& sourceName) { diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 8307d53a4f..ac1244652a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -585,6 +585,8 @@ namespace MWWorld /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition); + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos); + virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName); diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 8151091b2d..84902d8c29 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -80,6 +80,31 @@ void ESM::CreatureStats::load (ESMReader &esm) mSpells.load(esm); mActiveSpells.load(esm); mAiSequence.load(esm); + + while (esm.isNextSub("SUMM")) + { + int magicEffect; + esm.getHT(magicEffect); + int actorId; + esm.getHNT (actorId, "ACID"); + mSummonedCreatureMap[magicEffect] = actorId; + } + + while (esm.isNextSub("GRAV")) + { + int actorId; + esm.getHT(actorId); + mSummonGraveyard.push_back(actorId); + } + + mHasAiSettings = false; + esm.getHNOT(mHasAiSettings, "AISE"); + + if (mHasAiSettings) + { + for (int i=0; i<4; ++i) + mAiSettings[i].load(esm); + } } void ESM::CreatureStats::save (ESMWriter &esm) const @@ -162,4 +187,19 @@ void ESM::CreatureStats::save (ESMWriter &esm) const mSpells.save(esm); mActiveSpells.save(esm); mAiSequence.save(esm); + + for (std::map::const_iterator it = mSummonedCreatureMap.begin(); it != mSummonedCreatureMap.end(); ++it) + { + esm.writeHNT ("SUMM", it->first); + esm.writeHNT ("ACID", it->second); + } + + for (std::vector::const_iterator it = mSummonGraveyard.begin(); it != mSummonGraveyard.end(); ++it) + { + esm.writeHNT ("GRAV", *it); + } + + esm.writeHNT("AISE", mHasAiSettings); + for (int i=0; i<4; ++i) + mAiSettings[i].save(esm); } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index c8dc97403c..628bfee0a9 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -26,6 +26,12 @@ namespace ESM AiSequence::AiSequence mAiSequence; + bool mHasAiSettings; + StatState mAiSettings[4]; + + std::map mSummonedCreatureMap; + std::vector mSummonGraveyard; + ESM::TimeStamp mTradeTime; int mGoldPool; int mActorId;