diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 2dfa34ba3..0459d5341 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -200,6 +200,9 @@ namespace MWBase virtual MWWorld::Ptr searchPtrViaHandle (const std::string& handle) = 0; ///< Return a pointer to a liveCellRef with the given Ogre handle or Ptr() if not found + virtual MWWorld::Ptr searchPtrViaActorId (int actorId) = 0; + ///< Search is limited to the active cells. + /// \todo enable reference in the OGRE scene virtual void enable (const MWWorld::Ptr& ptr) = 0; @@ -390,14 +393,10 @@ namespace MWBase virtual void setupPlayer() = 0; virtual void renderPlayer() = 0; - /// if activated, should this door be opened or closed? - virtual bool getOpenOrCloseDoor(const MWWorld::Ptr& door) = 0; - - /// activate (open or close) an non-teleport door + /// open or close a non-teleport door (depending on current state) virtual void activateDoor(const MWWorld::Ptr& door) = 0; - - /// Is door currently opening/closing? - virtual bool getIsMovingDoor(const MWWorld::Ptr& door) = 0; + /// open or close a non-teleport door as specified + virtual void activateDoor(const MWWorld::Ptr& door, bool open) = 0; virtual bool getPlayerStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is standing on \a object diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4c6ab1c75..dc23b63f3 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -164,7 +164,11 @@ namespace MWClass { const std::string model = getModel(ptr); if(!model.empty()) + { physics.addActor(ptr); + if (getCreatureStats(ptr).isDead()) + MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); + } MWBase::Environment::get().getMechanicsManager()->add(ptr); } diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 732038b2f..be01b848a 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -2,6 +2,7 @@ #include "creaturelevlist.hpp" #include +#include #include "../mwmechanics/levelledlist.hpp" @@ -11,7 +12,9 @@ namespace { struct CreatureLevListCustomData : public MWWorld::CustomData { - // TODO: save the creature we spawned here + // actorId of the creature we spawned + int mSpawnActorId; + virtual MWWorld::CustomData *clone() const; }; @@ -38,6 +41,25 @@ namespace MWClass void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, MWRender::RenderingInterface &renderingInterface) const { ensureCustomData(ptr); + + CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + if (customData.mSpawnActorId != -1) + return; // TODO: handle respawning + + + MWWorld::LiveCellRef *ref = + ptr.get(); + + std::string id = MWMechanics::getLevelledItem(ref->mBase, true); + + if (!id.empty()) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + MWWorld::ManualRef ref(store, id); + ref.getPtr().getCellRef().mPos = ptr.getCellRef().mPos; + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().mPos); + customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId(); + } } void CreatureLevList::ensureCustomData(const MWWorld::Ptr &ptr) const @@ -45,22 +67,29 @@ namespace MWClass if (!ptr.getRefData().getCustomData()) { std::auto_ptr data (new CreatureLevListCustomData); - - MWWorld::LiveCellRef *ref = - ptr.get(); - - std::string id = MWMechanics::getLevelledItem(ref->mBase, true); - - if (!id.empty()) - { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - MWWorld::ManualRef ref(store, id); - ref.getPtr().getCellRef().mPos = ptr.getCellRef().mPos; - // TODO: hold on to this for respawn purposes later - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().mPos); - } + data->mSpawnActorId = -1; ptr.getRefData().setCustomData(data.release()); } } + + void CreatureLevList::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const + { + const ESM::CreatureLevListState& state2 = dynamic_cast (state); + + ensureCustomData(ptr); + CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + customData.mSpawnActorId = state2.mSpawnActorId; + } + + void CreatureLevList::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const + { + ESM::CreatureLevListState& state2 = dynamic_cast (state); + + ensureCustomData(ptr); + CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + state2.mSpawnActorId = customData.mSpawnActorId; + } } diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index d2c02043e..b67fb5523 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -19,6 +19,14 @@ namespace MWClass virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering + + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. }; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 984e21e72..06f0619ce 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -2,6 +2,7 @@ #include "door.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -17,12 +18,28 @@ #include "../mwworld/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/actiontrap.hpp" +#include "../mwworld/customdata.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +namespace +{ + struct DoorCustomData : public MWWorld::CustomData + { + int mDoorState; // 0 = nothing, 1 = opening, 2 = closing + + virtual MWWorld::CustomData *clone() const; + }; + + MWWorld::CustomData *DoorCustomData::clone() const + { + return new DoorCustomData (*this); + } +} + namespace MWClass { void Door::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const @@ -38,6 +55,14 @@ namespace MWClass const std::string model = getModel(ptr); if(!model.empty()) physics.addObject(ptr); + + // Resume the door's opening/closing animation if it wasn't finished + ensureCustomData(ptr); + const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + if (customData.mDoorState > 0) + { + MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState == 1 ? true : false); + } } std::string Door::getModel(const MWWorld::Ptr &ptr) const @@ -125,7 +150,14 @@ namespace MWClass { // animated door boost::shared_ptr action(new MWWorld::ActionDoor(ptr)); - if (MWBase::Environment::get().getWorld()->getOpenOrCloseDoor(ptr)) + int doorstate = getDoorState(ptr); + bool opening = true; + if (doorstate == 1) + opening = false; + if (doorstate == 0 && ptr.getRefData().getLocalRotation().rot[2] != 0) + opening = false; + + if (opening) { MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, closeSound, 0.5); @@ -262,4 +294,48 @@ namespace MWClass return MWWorld::Ptr(&cell.get().insert(*ref), &cell); } + + void Door::ensureCustomData(const MWWorld::Ptr &ptr) const + { + if (!ptr.getRefData().getCustomData()) + { + std::auto_ptr data(new DoorCustomData); + + data->mDoorState = 0; + ptr.getRefData().setCustomData(data.release()); + } + } + + int Door::getDoorState (const MWWorld::Ptr &ptr) const + { + ensureCustomData(ptr); + const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + return customData.mDoorState; + } + + void Door::setDoorState (const MWWorld::Ptr &ptr, int state) const + { + ensureCustomData(ptr); + DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + customData.mDoorState = state; + } + + void Door::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const + { + ensureCustomData(ptr); + DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + + const ESM::DoorState& state2 = dynamic_cast(state); + customData.mDoorState = state2.mDoorState; + } + + void Door::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const + { + ensureCustomData(ptr); + const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + + ESM::DoorState& state2 = dynamic_cast(state); + state2.mDoorState = customData.mDoorState; + } + } diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index bddc46728..12b360aa8 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -9,6 +9,8 @@ namespace MWClass { class Door : public MWWorld::Class { + void ensureCustomData (const MWWorld::Ptr& ptr) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; @@ -48,7 +50,20 @@ namespace MWClass static void registerSelf(); virtual std::string getModel(const MWWorld::Ptr &ptr) const; - private: + + /// 0 = nothing, 1 = opening, 2 = closing + virtual int getDoorState (const MWWorld::Ptr &ptr) const; + /// This does not actually cause the door to move. Use World::activateDoor instead. + virtual void setDoorState (const MWWorld::Ptr &ptr, int state) const; + + + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4c9b683a4..845fffbc0 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -388,6 +388,8 @@ namespace MWClass { physics.addActor(ptr); MWBase::Environment::get().getMechanicsManager()->add(ptr); + if (getCreatureStats(ptr).isDead()) + MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); } bool Npc::isPersistent(const MWWorld::Ptr &actor) const @@ -618,7 +620,8 @@ namespace MWClass // NOTE: 'object' and/or 'attacker' may be empty. // Attacking peaceful NPCs is a crime - if (!attacker.isEmpty() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() <= 30) + // anything below 80 is considered peaceful (see Actors::updateActor) + if (!attacker.isEmpty() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() < 80) MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); getCreatureStats(ptr).setAttacked(true); diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp index 9c4ea2d29..b8be9dcb8 100644 --- a/apps/openmw/mwgui/companionitemmodel.cpp +++ b/apps/openmw/mwgui/companionitemmodel.cpp @@ -10,23 +10,23 @@ namespace MWGui { } - void CompanionItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) + MWWorld::Ptr CompanionItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) { if (mActor.getClass().isNpc()) { - MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor); - stats.modifyProfit(MWWorld::Class::get(item.mBase).getValue(item.mBase) * count); + MWMechanics::NpcStats& stats = mActor.getClass().getNpcStats(mActor); + stats.modifyProfit(item.mBase.getClass().getValue(item.mBase) * count); } - InventoryItemModel::copyItem(item, count, setNewOwner); + return InventoryItemModel::copyItem(item, count, setNewOwner); } void CompanionItemModel::removeItem (const ItemStack& item, size_t count) { if (mActor.getClass().isNpc()) { - MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor); - stats.modifyProfit(-MWWorld::Class::get(item.mBase).getValue(item.mBase) * count); + MWMechanics::NpcStats& stats = mActor.getClass().getNpcStats(mActor); + stats.modifyProfit(-item.mBase.getClass().getValue(item.mBase) * count); } InventoryItemModel::removeItem(item, count); diff --git a/apps/openmw/mwgui/companionitemmodel.hpp b/apps/openmw/mwgui/companionitemmodel.hpp index 49c58c896..172fa9508 100644 --- a/apps/openmw/mwgui/companionitemmodel.hpp +++ b/apps/openmw/mwgui/companionitemmodel.hpp @@ -13,7 +13,7 @@ namespace MWGui public: CompanionItemModel (const MWWorld::Ptr& actor); - virtual void copyItem (const ItemStack& item, size_t count, bool setNewOwner); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner); virtual void removeItem (const ItemStack& item, size_t count); }; diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index e702c073d..b5b498e5e 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -71,12 +71,12 @@ ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item) return -1; } -void ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner) +MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner) { const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1]; if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source)) throw std::runtime_error("Item to copy needs to be from a different container!"); - source.getClass().getContainerStore(source).add(item.mBase, count, source); + return *source.getClass().getContainerStore(source).add(item.mBase, count, source); } void ContainerItemModel::removeItem (const ItemStack& item, size_t count) diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp index 4b9b4ff99..7ced6ae34 100644 --- a/apps/openmw/mwgui/containeritemmodel.hpp +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -21,7 +21,7 @@ namespace MWGui virtual ModelIndex getIndex (ItemStack item); virtual size_t getItemCount(); - virtual void copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); virtual void update(); diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index c0847929b..718c6d991 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -40,11 +40,11 @@ ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item) return -1; } -void InventoryItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner) +MWWorld::Ptr InventoryItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner) { if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) throw std::runtime_error("Item to copy needs to be from a different container!"); - mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor, setNewOwner); + return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor, setNewOwner); } @@ -59,16 +59,20 @@ void InventoryItemModel::removeItem (const ItemStack& item, size_t count) throw std::runtime_error("Not enough items in the stack to remove"); } -void InventoryItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) +MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) { bool setNewOwner = false; // Are you dead? Then you wont need that anymore - if (mActor.getClass().isActor() && mActor.getClass().getCreatureStats(mActor).isDead()) + if (mActor.getClass().isActor() && mActor.getClass().getCreatureStats(mActor).isDead() + // Make sure that the item is actually owned by the dead actor + // Prevents a potential exploit for resetting the owner of any item, by placing the item in a corpse + && Misc::StringUtils::ciEqual(item.mBase.getCellRef().mOwner, mActor.getCellRef().mRefID)) setNewOwner = true; - otherModel->copyItem(item, count, setNewOwner); + MWWorld::Ptr ret = otherModel->copyItem(item, count, setNewOwner); removeItem(item, count); + return ret; } void InventoryItemModel::update() diff --git a/apps/openmw/mwgui/inventoryitemmodel.hpp b/apps/openmw/mwgui/inventoryitemmodel.hpp index 78dcc9497..f58ee2939 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.hpp +++ b/apps/openmw/mwgui/inventoryitemmodel.hpp @@ -15,11 +15,11 @@ namespace MWGui virtual ModelIndex getIndex (ItemStack item); virtual size_t getItemCount(); - virtual void copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); /// Move items from this model to \a otherModel. - virtual void moveItem (const ItemStack& item, size_t count, ItemModel* otherModel); + virtual MWWorld::Ptr moveItem (const ItemStack& item, size_t count, ItemModel* otherModel); virtual void update(); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index d6da3b30e..4d52d59bb 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -408,14 +408,8 @@ namespace MWGui if (mDragAndDrop->mSourceModel != mTradeModel) { - // add item to the player's inventory - MWWorld::ContainerStore& invStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); - MWWorld::ContainerStoreIterator it = invStore.begin(); - - it = invStore.add(ptr, mDragAndDrop->mDraggedCount, mPtr); - - mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount); - ptr = *it; + // Move item to the player's inventory + ptr = mDragAndDrop->mSourceModel->moveItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount, mTradeModel); } useItem(ptr); } diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 7b2ea4d4a..2cdd8a2e6 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -71,10 +71,11 @@ namespace MWGui { } - void ItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) + MWWorld::Ptr ItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) { - otherModel->copyItem(item, count); + MWWorld::Ptr ret = otherModel->copyItem(item, count); removeItem(item, count); + return ret; } @@ -83,10 +84,9 @@ namespace MWGui delete mSourceModel; } - void ProxyItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner) + MWWorld::Ptr ProxyItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner) { - // no need to use mapToSource since itemIndex refers to an index in the sourceModel - mSourceModel->copyItem (item, count, setNewOwner); + return mSourceModel->copyItem (item, count, setNewOwner); } void ProxyItemModel::removeItem (const ItemStack& item, size_t count) diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index 684771b86..21c5477d0 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -56,10 +56,10 @@ namespace MWGui virtual void update() = 0; /// Move items from this model to \a otherModel. - virtual void moveItem (const ItemStack& item, size_t count, ItemModel* otherModel); + virtual MWWorld::Ptr moveItem (const ItemStack& item, size_t count, ItemModel* otherModel); /// @param setNewOwner Set the copied item's owner to the actor we are copying to, or keep the original owner? - virtual void copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) = 0; + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) = 0; virtual void removeItem (const ItemStack& item, size_t count) = 0; private: @@ -73,7 +73,7 @@ namespace MWGui { public: virtual ~ProxyItemModel(); - virtual void copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); virtual ModelIndex getIndex (ItemStack item); diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 0cd665a87..d5ece298b 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -21,7 +21,7 @@ namespace MWGui { void EffectSourceVisitor::visit (MWMechanics::EffectKey key, - const std::string& sourceName, const std::string& casterHandle, + const std::string& sourceName, int casterActorId, float magnitude, float remainingTime) { MagicEffectInfo newEffectSource; diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index 82d8b1f2d..7df9ad8b9 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -45,7 +45,7 @@ namespace MWGui virtual ~EffectSourceVisitor() {} virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, const std::string& casterHandle, + const std::string& sourceName, int casterActorId, float magnitude, float remainingTime = -1); }; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 86db207a4..e64a736c3 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -53,9 +53,9 @@ namespace MWMechanics { const MWWorld::TimeStamp& start = iter->second.mTimeStamp; - const std::vector& effects = iter->second.mEffects; + const std::vector& effects = iter->second.mEffects; - for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) + for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) { int duration = effectIt->mDuration; MWWorld::TimeStamp end = start; @@ -63,7 +63,7 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); if (end>now) - mEffects.add(effectIt->mKey, MWMechanics::EffectParam(effectIt->mMagnitude)); + mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude)); } } } @@ -91,11 +91,11 @@ namespace MWMechanics double ActiveSpells::timeToExpire (const TIterator& iterator) const { - const std::vector& effects = iterator->second.mEffects; + const std::vector& effects = iterator->second.mEffects; int duration = 0; - for (std::vector::const_iterator iter (effects.begin()); + for (std::vector::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) { if (iter->mDuration > duration) @@ -132,8 +132,8 @@ namespace MWMechanics return mSpells; } - void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector effects, - const std::string &displayName, const std::string& casterHandle) + void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector effects, + const std::string &displayName, int casterActorId) { bool exists = false; for (TContainer::const_iterator it = begin(); it != end(); ++it) @@ -146,7 +146,7 @@ namespace MWMechanics params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); params.mEffects = effects; params.mDisplayName = displayName; - params.mCasterHandle = casterHandle; + params.mCasterActorId = casterActorId; if (!exists || stack) mSpells.insert (std::make_pair(id, params)); @@ -168,7 +168,7 @@ namespace MWMechanics { float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); + for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end(); ++effectIt) { std::string name = it->second.mDisplayName; @@ -178,7 +178,7 @@ namespace MWMechanics float magnitude = effectIt->mMagnitude; if (magnitude) - visitor.visit(effectIt->mKey, name, it->second.mCasterHandle, magnitude, remainingTime); + visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), name, it->second.mCasterActorId, magnitude, remainingTime); } } } @@ -200,10 +200,10 @@ namespace MWMechanics { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) { - for (std::vector::iterator effectIt = it->second.mEffects.begin(); + for (std::vector::iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end();) { - if (effectIt->mKey.mId == effectId) + if (effectIt->mEffectId == effectId) effectIt = it->second.mEffects.erase(effectIt); else ++effectIt; @@ -212,16 +212,16 @@ namespace MWMechanics mSpellsChanged = true; } - void ActiveSpells::purge(const std::string &actorHandle) + void ActiveSpells::purge(int casterActorId) { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) { - for (std::vector::iterator effectIt = it->second.mEffects.begin(); + for (std::vector::iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end();) { - const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mKey.mId); + const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectId); if (effect->mData.mFlags & ESM::MagicEffect::CasterLinked - && it->second.mCasterHandle == actorHandle) + && it->second.mCasterActorId == casterActorId) effectIt = it->second.mEffects.erase(effectIt); else ++effectIt; @@ -229,4 +229,41 @@ namespace MWMechanics } mSpellsChanged = true; } + + void ActiveSpells::clear() + { + mSpells.clear(); + mSpellsChanged = true; + } + + void ActiveSpells::writeState(ESM::ActiveSpells &state) const + { + for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp + ESM::ActiveSpells::ActiveSpellParams params; + params.mEffects = it->second.mEffects; + params.mCasterActorId = it->second.mCasterActorId; + params.mDisplayName = it->second.mDisplayName; + params.mTimeStamp = it->second.mTimeStamp.toEsm(); + + state.mSpells.insert (std::make_pair(it->first, params)); + } + } + + void ActiveSpells::readState(const ESM::ActiveSpells &state) + { + for (ESM::ActiveSpells::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) + { + // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp + ActiveSpellParams params; + params.mEffects = it->second.mEffects; + params.mCasterActorId = it->second.mCasterActorId; + params.mDisplayName = it->second.mDisplayName; + params.mTimeStamp = MWWorld::TimeStamp(it->second.mTimeStamp); + + mSpells.insert (std::make_pair(it->first, params)); + mSpellsChanged = true; + } + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 7a40afb4c..6c4c93128 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -10,6 +10,7 @@ #include "magiceffects.hpp" #include +#include namespace MWMechanics { @@ -21,29 +22,24 @@ namespace MWMechanics { public: - // Parameters of an effect concerning lasting effects. - // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc. - // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster. - struct Effect - { - float mMagnitude; - EffectKey mKey; - float mDuration; - }; + typedef ESM::ActiveEffect ActiveEffect; struct ActiveSpellParams { - std::vector mEffects; + std::vector mEffects; MWWorld::TimeStamp mTimeStamp; std::string mDisplayName; - // Handle to the caster that that inflicted this spell on us - std::string mCasterHandle; + // The caster that inflicted this spell on us + int mCasterActorId; }; typedef std::multimap TContainer; typedef TContainer::const_iterator TIterator; + void readState (const ESM::ActiveSpells& state); + void writeState (ESM::ActiveSpells& state) const; + private: mutable TContainer mSpells; @@ -76,10 +72,9 @@ namespace MWMechanics /// \param stack If false, the spell is not added if one with the same ID exists already. /// \param effects /// \param displayName Name for display in magic menu. - /// \param casterHandle /// - void addSpell (const std::string& id, bool stack, std::vector effects, - const std::string& displayName, const std::string& casterHandle); + void addSpell (const std::string& id, bool stack, std::vector effects, + const std::string& displayName, int casterActorId); /// Removes the active effects from this spell/potion/.. with \a id void removeEffects (const std::string& id); @@ -90,8 +85,11 @@ namespace MWMechanics /// Remove all active effects, if roll succeeds (for each effect) void purgeAll (float chance); - /// Remove all effects with CASTER_LINKED flag that were cast by \a actorHandle - void purge (const std::string& actorHandle); + /// Remove all effects with CASTER_LINKED flag that were cast by \a casterActorId + void purge (int casterActorId); + + /// Remove all spells + void clear(); bool isSpellActive (std::string id) const; ///< case insensitive diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b935284d3..a0616258c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -38,9 +38,12 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a { if (bound) { - MWWorld::Ptr newPtr = *actor.getClass().getContainerStore(actor).add(item, 1, actor); - MWWorld::ActionEquip action(newPtr); - action.execute(actor); + if (actor.getClass().getContainerStore(actor).count(item) == 0) + { + MWWorld::Ptr newPtr = *actor.getClass().getContainerStore(actor).add(item, 1, actor); + MWWorld::ActionEquip action(newPtr); + action.execute(actor); + } } else { @@ -116,7 +119,7 @@ namespace MWMechanics : mCreature(trappedCreature) {} virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, const std::string& casterHandle, + const std::string& sourceName, int casterActorId, float magnitude, float remainingTime = -1) { if (key.mId != ESM::MagicEffect::Soultrap) @@ -126,7 +129,7 @@ namespace MWMechanics MWBase::World* world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr caster = world->searchPtrViaHandle(casterHandle); + MWWorld::Ptr caster = world->searchPtrViaActorId(casterActorId); if (caster.isEmpty() || !caster.getClass().isActor()) return; @@ -534,28 +537,54 @@ namespace MWMechanics MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); ref.getPtr().getCellRef().mPos = ipos; - // TODO: Add AI to follow player and fight for him - AiFollow package(ptr.getRefData().getHandle()); - MWWorld::Class::get (ref.getPtr()).getCreatureStats (ref.getPtr()).getAiSequence().stack(package, ptr); + MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); + + // Make the summoned creature follow its master and help in fights + AiFollow package(ptr); + summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); + int creatureActorId = summonedCreatureStats.getActorId(); + + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + // TODO: VFX_SummonStart, VFX_SummonEnd - creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos).getRefData().getHandle())); + creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, creatureActorId)); } } else { - std::string handle = creatureStats.mSummonedCreatures[it->first]; - // 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. - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle(handle); + // Summon lifetime has expired. Try to delete the creature. + int actorId = creatureStats.mSummonedCreatures[it->first]; + creatureStats.mSummonedCreatures.erase(it->first); + + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(actorId); 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); creatureStats.mSummonedCreatures.erase(it->first); } + 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); + } } } } + + for (std::vector::iterator it = creatureStats.mSummonGraveyard.begin(); it != creatureStats.mSummonGraveyard.end(); ) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(*it); + if (!ptr.isEmpty()) + { + it = creatureStats.mSummonGraveyard.erase(it); + MWBase::Environment::get().getWorld()->deleteObject(ptr); + } + else + ++it; + } } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) @@ -730,7 +759,7 @@ namespace MWMechanics && MWBase::Environment::get().getWorld()->getLOS(ptr, player) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) { - creatureStats.getAiSequence().stack(AiPursue(player.getClass().getId(player)), ptr); + creatureStats.getAiSequence().stack(AiPursue(player), ptr); creatureStats.setAlarmed(true); npcStats.setCrimeId(MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId()); } @@ -759,7 +788,7 @@ namespace MWMechanics else if (!creatureStats.isHostile()) { if (ptr.getClass().isClass(ptr, "Guard")) - creatureStats.getAiSequence().stack(AiPursue(player.getClass().getId(player)), ptr); + creatureStats.getAiSequence().stack(AiPursue(player), ptr); else { MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); @@ -781,7 +810,7 @@ namespace MWMechanics } } - void Actors::addActor (const MWWorld::Ptr& ptr) + void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) { // erase previous death events since we are currently only tracking them while in an active cell MWWorld::Class::get(ptr).getCreatureStats(ptr).clearHasDied(); @@ -790,6 +819,8 @@ namespace MWMechanics MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); mActors.insert(std::make_pair(ptr, new CharacterController(ptr, anim))); + if (updateImmediately) + mActors[ptr]->update(0); } void Actors::removeActor (const MWWorld::Ptr& ptr) @@ -912,18 +943,17 @@ namespace MWMechanics continue; } - // Make sure spell effects with CasterLinked flag are removed - // TODO: would be nice not to do this all the time... - for (PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) - { - MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); - spells.purge(iter->first.getRefData().getHandle()); - } - if (iter->second->kill()) { ++mDeathCount[cls.getId(iter->first)]; + // Make sure spell effects with CasterLinked flag are removed + for (PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) + { + MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); + spells.purge(stats.getActorId()); + } + // Apply soultrap if (iter->first.getTypeName() == typeid(ESM::Creature).name()) { @@ -934,6 +964,7 @@ namespace MWMechanics // Reset magic effects and recalculate derived effects // One case where we need this is to make sure bound items are removed upon death stats.setMagicEffects(MWMechanics::MagicEffects()); + stats.getActiveSpells().clear(); calculateCreatureStatModifiers(iter->first, 0); MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index f7dff1058..e42672bad 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -58,7 +58,7 @@ namespace MWMechanics /// paused we may want to do it manually (after equipping permanent enchantment) void updateMagicEffects (const MWWorld::Ptr& ptr) { adjustMagicEffects(ptr); } - void addActor (const MWWorld::Ptr& ptr); + void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false); ///< Register an actor for stats management /// /// \note Dead actors are ignored. diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index a206d27ca..44eacb0d1 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -45,7 +45,7 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor,float duration return true; // We have tried backing up for more than one second, we've probably cleared it } - if(!MWBase::Environment::get().getWorld()->getIsMovingDoor(mDoorPtr)) + if (!mDoorPtr.getClass().getDoorState(mDoorPtr)) return true; //Door is no longer opening ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index 85bb09fc8..c1b326198 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -11,9 +11,9 @@ namespace MWMechanics { /// \brief AiPackage to have an actor avoid an opening door /** The AI will retreat from the door until it has finished opening, walked far away from it, or one second has passed, in an attempt to avoid it - **/ - class AiAvoidDoor : public AiPackage - { + **/ + class AiAvoidDoor : public AiPackage + { public: /// Avoid door until the door is fully open AiAvoidDoor(const MWWorld::Ptr& doorPtr); @@ -29,10 +29,10 @@ namespace MWMechanics private: float mDuration; - const MWWorld::Ptr& mDoorPtr; + MWWorld::Ptr mDoorPtr; ESM::Position mLastPos; - float mAdjAngle; - }; -} -#endif + float mAdjAngle; + }; +} +#endif diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8464cc419..72cb8f574 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -81,7 +81,7 @@ namespace MWMechanics // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp AiCombat::AiCombat(const MWWorld::Ptr& actor) : - mTarget(actor), + mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()), mTimerAttack(0), mTimerReact(0), mTimerCombatMove(0), @@ -153,7 +153,9 @@ namespace MWMechanics || actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0) return true; - if(mTarget.getClass().getCreatureStats(mTarget).isDead()) + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + + if(target.getClass().getCreatureStats(target).isDead()) return true; //Update every frame @@ -325,7 +327,7 @@ namespace MWMechanics ESM::Position pos = actor.getRefData().getPosition(); Ogre::Vector3 vActorPos(pos.pos); - Ogre::Vector3 vTargetPos(mTarget.getRefData().getPosition().pos); + Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos); Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; bool isStuck = false; @@ -396,7 +398,7 @@ namespace MWMechanics else // remote pathfinding { bool preferShortcut = false; - bool inLOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget); + bool inLOS = MWBase::Environment::get().getWorld()->getLOS(actor, target); if(mReadyToAttack) isStuck = false; @@ -432,7 +434,7 @@ namespace MWMechanics mFollowTarget = false; - buildNewPath(actor); //may fail to build a path, check before use + 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]); @@ -476,9 +478,9 @@ namespace MWMechanics //less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing) //then start attacking float speed1 = actorCls.getSpeed(actor); - float speed2 = mTarget.getClass().getSpeed(mTarget); - if(mTarget.getClass().getMovementSettings(mTarget).mPosition[0] == 0 - && mTarget.getClass().getMovementSettings(mTarget).mPosition[1] == 0) + 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; @@ -570,9 +572,9 @@ namespace MWMechanics return false; } - void AiCombat::buildNewPath(const MWWorld::Ptr& actor) + void AiCombat::buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target) { - Ogre::Vector3 newPathTarget = Ogre::Vector3(mTarget.getRefData().getPosition().pos); + Ogre::Vector3 newPathTarget = Ogre::Vector3(target.getRefData().getPosition().pos); float dist; @@ -627,9 +629,12 @@ namespace MWMechanics return 1; } - const std::string &AiCombat::getTargetId() const + std::string AiCombat::getTargetId() const { - return mTarget.getRefData().getHandle(); + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + if (target.isEmpty()) + return ""; + return target.getRefData().getHandle(); } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 833b0063c..9125e2832 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -31,7 +31,7 @@ namespace MWMechanics virtual unsigned int getPriority() const; ///Returns target ID - const std::string &getTargetId() const; + std::string getTargetId() const; private: PathFinder mPathFinder; @@ -53,7 +53,7 @@ namespace MWMechanics ESM::Position mLastPos; MWMechanics::Movement mMovement; - MWWorld::Ptr mTarget; + int mTargetActorId; const MWWorld::CellStore* mCell; ObstacleCheck mObstacleCheck; @@ -63,7 +63,7 @@ namespace MWMechanics MWWorld::CellRefList::List::iterator mDoorIter; MWWorld::CellRefList& mDoors; - void buildNewPath(const MWWorld::Ptr& actor); + void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); }; } diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 0430adf06..f3ce7143a 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -8,6 +8,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/timestamp.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "steering.hpp" #include "movement.hpp" @@ -19,8 +21,8 @@ namespace MWMechanics { - AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) - : mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration) + AiEscort::AiEscort(const MWWorld::Ptr& actor, int duration, float x, float y, float z) + : mActorId(actor.getClass().getCreatureStats(actor).getActorId()), mX(x), mY(y), mZ(z), mDuration(duration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { @@ -38,8 +40,8 @@ namespace MWMechanics } } - AiEscort::AiEscort(const std::string &actorId, const std::string &cellId,int duration, float x, float y, float z) - : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration) + AiEscort::AiEscort(const MWWorld::Ptr& actor, const std::string &cellId,int duration, float x, float y, float z) + : mActorId(actor.getClass().getCreatureStats(actor).getActorId()), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { @@ -75,7 +77,14 @@ namespace MWMechanics return true; } - const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); + const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId); + if (follower.isEmpty()) + { + // The follower disappeared + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + return true; + } + const float* const leaderPos = actor.getRefData().getPosition().pos; const float* const followerPos = follower.getRefData().getPosition().pos; double differenceBetween[3]; @@ -89,7 +98,7 @@ namespace MWMechanics if(distanceBetweenResult <= mMaxDist * mMaxDist) { - if(pathTo(actor,ESM::Pathgrid::Point(mX,mY,mZ),duration)) //Returns true on path complete + if(pathTo(actor,ESM::Pathgrid::Point(mX,mY,mZ),duration)) //Returns true on path complete return true; mMaxDist = 470; } diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 3771417fa..b8dc9d6e0 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -15,11 +15,11 @@ namespace MWMechanics /// Implementation of AiEscort /** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time \implement AiEscort **/ - AiEscort(const std::string &actorId,int duration, float x, float y, float z); + AiEscort(const MWWorld::Ptr& actor,int duration, float x, float y, float z); /// Implementation of AiEscortCell /** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time \implement AiEscortCell **/ - AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z); + AiEscort(const MWWorld::Ptr& actor,const std::string &cellId,int duration, float x, float y, float z); virtual AiEscort *clone() const; @@ -28,7 +28,7 @@ namespace MWMechanics virtual int getTypeId() const; private: - std::string mActorId; + int mActorId; std::string mCellId; float mX; float mY; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f1296a949..d747781a3 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -11,23 +11,26 @@ #include "steering.hpp" -MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), AiPackage() +MWMechanics::AiFollow::AiFollow(const MWWorld::Ptr& actor,float duration, float x, float y, float z) +: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mCellId(""), AiPackage() { + mActorId = actor.getClass().getCreatureStats(actor).getActorId(); } -MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), AiPackage() +MWMechanics::AiFollow::AiFollow(const MWWorld::Ptr& actor,const std::string &cellId,float duration, float x, float y, float z) +: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mCellId(cellId), AiPackage() { + mActorId = actor.getClass().getCreatureStats(actor).getActorId(); } -MWMechanics::AiFollow::AiFollow(const std::string &actorId) -: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId(""), AiPackage() +MWMechanics::AiFollow::AiFollow(const MWWorld::Ptr& actor) +: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mCellId(""), AiPackage() { + mActorId = actor.getClass().getCreatureStats(actor).getActorId(); } bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); //The target to follow + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId); //The target to follow if(target == MWWorld::Ptr()) return true; //Target doesn't exist @@ -75,7 +78,8 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) std::string MWMechanics::AiFollow::getFollowedActor() { - return mActorId; + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId); //The target to follow + return target.getCellRef().mRefID; } MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 10a381410..91bdbdac2 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -10,16 +10,16 @@ namespace MWMechanics { /// \brief AiPackage for an actor to follow another actor/the PC /** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely - **/ - class AiFollow : public AiPackage - { + **/ + class AiFollow : public AiPackage + { public: /// Follow Actor for duration or until you arrive at a world position - AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); + AiFollow(const MWWorld::Ptr& actor,float duration, float X, float Y, float Z); /// Follow Actor for duration or until you arrive at a position in a cell - AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); + AiFollow(const MWWorld::Ptr& actor,const std::string &CellId,float duration, float X, float Y, float Z); /// Follow Actor indefinitively - AiFollow(const std::string &ActorId); + AiFollow(const MWWorld::Ptr& actor); virtual AiFollow *clone() const; @@ -38,8 +38,8 @@ namespace MWMechanics float mX; float mY; float mZ; - std::string mActorId; - std::string mCellId; - }; -} -#endif + int mActorId; // The actor we should follow + std::string mCellId; + }; +} +#endif diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 6e91ccb71..aa71af465 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -7,12 +7,14 @@ #include "../mwworld/action.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "steering.hpp" #include "movement.hpp" #include "creaturestats.hpp" -MWMechanics::AiPursue::AiPursue(const std::string &objectId) - : mObjectId(objectId) +MWMechanics::AiPursue::AiPursue(const MWWorld::Ptr& actor) + : mActorId(actor.getClass().getCreatureStats(actor).getActorId()) { } MWMechanics::AiPursue *MWMechanics::AiPursue::clone() const @@ -23,7 +25,7 @@ bool MWMechanics::AiPursue::execute (const MWWorld::Ptr& actor, float duration) { ESM::Position pos = actor.getRefData().getPosition(); //position of the actor - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId); //The target to follow if(target == MWWorld::Ptr()) return true; //Target doesn't exist @@ -33,8 +35,7 @@ bool MWMechanics::AiPursue::execute (const MWWorld::Ptr& actor, float duration) if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) { //Stop when you get close actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); - MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); //Arrest player + target.getClass().activate(target,actor).get()->execute(actor); //Arrest player return true; } else { diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 2eb533d62..90ff8df93 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -16,14 +16,14 @@ namespace MWMechanics { public: ///Constructor - /** \param objectId Actor to pursue **/ - AiPursue(const std::string &objectId); + /** \param actor Actor to pursue **/ + AiPursue(const MWWorld::Ptr& actor); virtual AiPursue *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual int getTypeId() const; private: - std::string mObjectId; + int mActorId; // The actor to pursue int mCellX; int mCellY; }; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 0b1da180d..8afe8402f 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -175,7 +175,8 @@ void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list) else if (it->mType == ESM::AI_Escort) { ESM::AITarget data = it->mTarget; - package = new MWMechanics::AiEscort(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(data.mId.toString(), false); + package = new MWMechanics::AiEscort(target, data.mDuration, data.mX, data.mY, data.mZ); } else if (it->mType == ESM::AI_Travel) { @@ -190,7 +191,8 @@ void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list) else //if (it->mType == ESM::AI_Follow) { ESM::AITarget data = it->mTarget; - package = new MWMechanics::AiFollow(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(data.mId.toString(), false); + package = new MWMechanics::AiFollow(target, data.mDuration, data.mX, data.mY, data.mZ); } mPackages.push_back(package); } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 280639f71..bf8ce8bdc 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -431,6 +431,11 @@ void CharacterController::playRandomDeath(float startpoint) mDeathState = static_cast(CharState_Death1 + (selected-1)); } + // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually. + mMovementState = CharState_None; + mAnimation->disable(mCurrentMovement); + mCurrentMovement = ""; + mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, false, 1.0f, "start", "stop", startpoint, 0); } @@ -1367,9 +1372,9 @@ bool CharacterController::kill() { if( isDead() ) { - //player's death animation is over if( mPtr.getRefData().getHandle()=="player" && !isAnimPlaying(mCurrentDeath) ) { + //player's death animation is over MWBase::Environment::get().getStateManager()->askLoadRecent(); } return false; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 5aea0210f..1b7caf34d 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -202,7 +202,9 @@ public: void skipAnim(); bool isAnimPlaying(const std::string &groupName); + /// @return false if the character has already been killed before bool kill(); + void resurrect(); bool isDead() const { return mDeathState != CharState_None; } diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 1482ee24e..57618e6e9 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -11,6 +11,8 @@ namespace MWMechanics { + int CreatureStats::sActorId = 0; + CreatureStats::CreatureStats() : mLevel (0), mDead (false), mDied (false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), @@ -20,7 +22,7 @@ namespace MWMechanics mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f), - mTradeTime(0,0), mGoldPool(0) + mTradeTime(0,0), mGoldPool(0), mActorId(-1) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; @@ -494,13 +496,15 @@ namespace MWMechanics state.mBlock = mBlock; state.mMovementFlags = mMovementFlags; state.mAttackStrength = mAttackStrength; - state.mFallHeight = mFallHeight; + state.mFallHeight = mFallHeight; // TODO: vertical velocity (move from PhysicActor to CreatureStats?) state.mLastHitObject = mLastHitObject; state.mRecalcDynamicStats = mRecalcDynamicStats; state.mDrawState = mDrawState; state.mLevel = mLevel; + state.mActorId = mActorId; mSpells.writeState(state.mSpells); + mActiveSpells.writeState(state.mActiveSpells); } void CreatureStats::readState (const ESM::CreatureStats& state) @@ -536,8 +540,10 @@ namespace MWMechanics mRecalcDynamicStats = state.mRecalcDynamicStats; mDrawState = DrawState_(state.mDrawState); mLevel = state.mLevel; + mActorId = state.mActorId; mSpells.readState(state.mSpells); + mActiveSpells.readState(state.mActiveSpells); } // Relates to NPC gold reset delay @@ -559,4 +565,34 @@ namespace MWMechanics { return mGoldPool; } + + int CreatureStats::getActorId() + { + if (mActorId==-1) + mActorId = sActorId++; + + return mActorId; + } + + bool CreatureStats::matchesActorId (int id) const + { + return mActorId!=-1 && id==mActorId; + } + + void CreatureStats::cleanup() + { + sActorId = 0; + } + + void CreatureStats::writeActorIdCounter (ESM::ESMWriter& esm) + { + esm.startRecord(ESM::REC_ACTC); + esm.writeHNT("COUN", sActorId); + esm.endRecord(ESM::REC_ACTC); + } + + void CreatureStats::readActorIdCounter (ESM::ESMReader& esm) + { + esm.getHNT(sActorId, "COUN"); + } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 2301f9a67..7d2dc1a62 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -24,6 +24,7 @@ namespace MWMechanics /// class CreatureStats { + static int sActorId; DrawState_ mDrawState; AttributeValue mAttributes[8]; DynamicStat mDynamic[3]; // health, magicka, fatigue @@ -58,6 +59,7 @@ namespace MWMechanics MWWorld::TimeStamp mTradeTime; // Relates to NPC gold reset delay int mGoldPool; // the pool of merchant gold not in inventory + int mActorId; protected: // These two are only set by NpcStats, but they are declared in CreatureStats to prevent using virtual methods. @@ -221,21 +223,39 @@ namespace MWMechanics void setLastHitObject(const std::string &objectid); const std::string &getLastHitObject() const; - // Note, this is just a cache to avoid checking the whole container store every frame TODO: Put it somewhere else? + // Note, this is just a cache to avoid checking the whole container store every frame. We don't need to store it in saves. + // TODO: Put it somewhere else? std::set mBoundItems; - // Same as above - std::map mSummonedCreatures; + + // 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); + static void writeActorIdCounter (ESM::ESMWriter& esm); + static void readActorIdCounter (ESM::ESMReader& esm); + // Relates to NPC gold reset delay void setTradeTime(MWWorld::TimeStamp tradeTime); MWWorld::TimeStamp getTradeTime() const; void setGoldPool(int pool); int getGoldPool() const; + + int getActorId(); + ///< Will generate an actor ID, if the actor does not have one yet. + + bool matchesActorId (int id) const; + ///< Check if \a id matches the actor ID of *this (if the actor does not have an ID + /// assigned this function will return false). + + static void cleanup(); }; } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 45abda21d..4fd5e159a 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -56,7 +56,7 @@ namespace MWMechanics struct EffectSourceVisitor { virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, const std::string& casterHandle, + const std::string& sourceName, int casterActorId, float magnitude, float remainingTime = -1) = 0; }; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 2b1f82b92..bf9a11d6e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -25,11 +25,11 @@ namespace bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, MWWorld::Ptr& victim) { const std::string& owner = item.getCellRef().mOwner; - bool isOwned = !owner.empty(); + bool isOwned = !owner.empty() && owner != "player"; const std::string& faction = item.getCellRef().mFaction; bool isFactionOwned = false; - if (!faction.empty()) + if (!faction.empty() && ptr.getClass().isNpc()) { const std::map& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks(); if (factions.find(Misc::StringUtils::lowerCase(faction)) == factions.end()) @@ -385,7 +385,7 @@ namespace MWMechanics // have been made for them. Make sure they're properly updated. MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); mActors.removeActor(ptr); - mActors.addActor(ptr); + mActors.addActor(ptr, true); } mActors.update(duration, paused); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index e6342e661..0843e3e54 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -140,7 +140,7 @@ namespace MWMechanics float x = (willpower + 0.1 * luck) * stats.getFatigueTerm(); // This makes spells that are easy to cast harder to resist and vice versa - if (spell != NULL && caster.getClass().isActor()) + if (spell != NULL && !caster.isEmpty() && caster.getClass().isActor()) { float castChance = getSpellSuccessChance(spell, caster); if (castChance > 0) @@ -189,6 +189,9 @@ namespace MWMechanics void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) { + if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) + return; + // If none of the effects need to apply, we can early-out bool found = false; for (std::vector::const_iterator iter (effects.mList.begin()); @@ -219,10 +222,12 @@ namespace MWMechanics } ESM::EffectList reflectedEffects; - std::vector appliedLastingEffects; + std::vector appliedLastingEffects; bool firstAppliedEffect = true; bool anyHarmfulEffect = false; + bool castByPlayer = (!caster.isEmpty() && caster.getRefData().getHandle() == "player"); + for (std::vector::const_iterator effectIt (effects.mList.begin()); effectIt!=effects.mList.end(); ++effectIt) { @@ -235,7 +240,7 @@ namespace MWMechanics if (!MWBase::Environment::get().getWorld()->isLevitationEnabled() && effectIt->mEffectID == ESM::MagicEffect::Levitate) { - if (caster.getRefData().getHandle() == "player") + if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); continue; } @@ -246,13 +251,13 @@ namespace MWMechanics effectIt->mEffectID == ESM::MagicEffect::Mark || effectIt->mEffectID == ESM::MagicEffect::Recall)) { - if (caster.getRefData().getHandle() == "player") + if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); continue; } // If player is healing someone, show the target's HP bar - if (caster.getRefData().getHandle() == "player" && target != caster + if (castByPlayer && target != caster && effectIt->mEffectID == ESM::MagicEffect::RestoreHealth && target.getClass().isActor()) MWBase::Environment::get().getWindowManager()->setEnemy(target); @@ -263,7 +268,7 @@ namespace MWMechanics anyHarmfulEffect = true; // If player is attempting to cast a harmful spell, show the target's HP bar - if (caster.getRefData().getHandle() == "player" && target != caster) + if (castByPlayer && target != caster) MWBase::Environment::get().getWindowManager()->setEnemy(target); // Try absorbing if it's a spell @@ -329,8 +334,9 @@ namespace MWMechanics bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); if (target.getClass().isActor() && hasDuration) { - ActiveSpells::Effect effect; - effect.mKey = MWMechanics::EffectKey(*effectIt); + ActiveSpells::ActiveEffect effect; + effect.mEffectId = effectIt->mEffectID; + effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; effect.mDuration = effectIt->mDuration; effect.mMagnitude = magnitude; @@ -338,17 +344,20 @@ namespace MWMechanics // For absorb effects, also apply the effect to the caster - but with a negative // magnitude, since we're transfering stats from the target to the caster - for (int i=0; i<5; ++i) + if (!caster.isEmpty() && caster.getClass().isActor()) { - if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) + for (int i=0; i<5; ++i) { - std::vector effects; - ActiveSpells::Effect effect_ = effect; - effect_.mMagnitude *= -1; - effects.push_back(effect_); - // Also make sure to set casterHandle = target, so that the effect on the caster gets purged when the target dies - caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, - effects, mSourceName, target.getRefData().getHandle()); + if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) + { + std::vector effects; + ActiveSpells::ActiveEffect effect_ = effect; + effect_.mMagnitude *= -1; + effects.push_back(effect_); + // Also make sure to set casterActorId = target, so that the effect on the caster gets purged when the target dies + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, + effects, mSourceName, target.getClass().getCreatureStats(target).getActorId()); + } } } } @@ -389,6 +398,7 @@ namespace MWMechanics else castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); + // TODO: VFX are no longer active after saving/reloading the game bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; // Note: in case of non actor, a free effect should be fine as well MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); @@ -405,12 +415,17 @@ namespace MWMechanics inflict(caster, target, reflectedEffects, range, true); if (!appliedLastingEffects.empty()) + { + int casterActorId = -1; + if (caster.getClass().isActor()) + casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, - mSourceName, caster.getRefData().getHandle()); + mSourceName, casterActorId); + } - if (anyHarmfulEffect && target.getClass().isActor() && target != caster - && target.getClass().getCreatureStats(target).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() <= 30) - MWBase::Environment::get().getMechanicsManager()->commitCrime(caster, target, MWBase::MechanicsManager::OT_Assault); + // Notify the target actor they've been hit + if (anyHarmfulEffect && target.getClass().isActor() && target != caster && caster.getClass().isActor()) + target.getClass().onHit(target, 0.f, true, MWWorld::Ptr(), caster, true); } void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude) @@ -431,7 +446,8 @@ namespace MWMechanics if (target.getCellRef().mLockLevel > 0) { MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); - MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); + if (!caster.isEmpty() && caster.getClass().isActor()) + MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); } target.getCellRef().mLockLevel = -abs(target.getCellRef().mLockLevel); //unlocks the door } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 44402fe7b..dce4b792e 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -61,9 +61,12 @@ namespace MWMechanics /// @note Auto detects if spell, ingredient or potion bool cast (const std::string& id); + /// @note \a target can be any type of object, not just actors. + /// @note \a caster can be any type of object, or even an empty object. void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false); + /// @note \a caster can be any type of object, or even an empty object. void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); }; diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index a326b7a6d..dee1a1b05 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -211,7 +211,7 @@ namespace MWMechanics random = it->second.at(i); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; - visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, "", magnitude); + visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, -1, magnitude); } } } diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index e53b53e58..8ed19925e 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -91,6 +91,7 @@ namespace MWScript std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); + MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPtr(actorID, true); Interpreter::Type_Float duration = runtime[0].mFloat; runtime.pop(); @@ -107,7 +108,7 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; igetPtr(actorID, true); std::string cellID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); @@ -145,7 +147,7 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; igetPtr(actorID, true); Interpreter::Type_Float duration = runtime[0].mFloat; runtime.pop(); @@ -297,7 +300,7 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; igetPtr(actorID, true); std::string cellID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); @@ -335,8 +339,8 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; ireadRecord (reader, n.val, contentFileMap); break; diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 627c05251..4378e179d 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -22,7 +22,6 @@ namespace MWWorld std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor); for(std::list::iterator it = followers.begin();it != followers.end();++it) { - std::cout << "teleporting someone!" << (*it).getCellRef().mRefID; executeImp(*it); } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 39f6538bb..5d2d8a517 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -12,10 +12,14 @@ #include #include #include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "ptr.hpp" #include "esmstore.hpp" #include "class.hpp" @@ -41,6 +45,22 @@ namespace return MWWorld::Ptr(); } + template + MWWorld::Ptr searchViaActorId (MWWorld::CellRefList& actorList, int actorId, + MWWorld::CellStore *cell) + { + for (typename MWWorld::CellRefList::List::iterator iter (actorList.mList.begin()); + iter!=actorList.mList.end(); ++iter) + { + MWWorld::Ptr actor (&*iter, cell); + + if (actor.getClass().getCreatureStats (actor).matchesActorId (actorId) && actor.getRefData().getCount() > 0) + return actor; + } + + return MWWorld::Ptr(); + } + template void writeReferenceCollection (ESM::ESMWriter& writer, const MWWorld::CellRefList& collection) @@ -323,6 +343,17 @@ namespace MWWorld return Ptr(); } + Ptr CellStore::searchViaActorId (int id) + { + if (Ptr ptr = ::searchViaActorId (mNpcs, id, this)) + return ptr; + + if (Ptr ptr = ::searchViaActorId (mCreatures, id, this)) + return ptr; + + return Ptr(); + } + float CellStore::getWaterLevel() const { return mWaterLevel; @@ -561,9 +592,9 @@ namespace MWWorld writeReferenceCollection (writer, mClothes); writeReferenceCollection (writer, mContainers); writeReferenceCollection (writer, mCreatures); - writeReferenceCollection (writer, mDoors); + writeReferenceCollection (writer, mDoors); writeReferenceCollection (writer, mIngreds); - writeReferenceCollection (writer, mCreatureLists); + writeReferenceCollection (writer, mCreatureLists); writeReferenceCollection (writer, mItemLists); writeReferenceCollection (writer, mLights); writeReferenceCollection (writer, mLockpicks); @@ -629,7 +660,7 @@ namespace MWWorld case ESM::REC_DOOR: - readReferenceCollection (reader, mDoors, contentFileMap); + readReferenceCollection (reader, mDoors, contentFileMap); break; case ESM::REC_INGR: @@ -639,7 +670,7 @@ namespace MWWorld case ESM::REC_LEVC: - readReferenceCollection (reader, mCreatureLists, contentFileMap); + readReferenceCollection (reader, mCreatureLists, contentFileMap); break; case ESM::REC_LEVI: diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index cc3036647..91c536b43 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -91,6 +91,9 @@ namespace MWWorld Ptr searchViaHandle (const std::string& handle); ///< Will return an empty Ptr if cell is not loaded. + Ptr searchViaActorId (int id); + ///< Will return an empty Ptr if cell is not loaded. + float getWaterLevel() const; void setWaterLevel (float level); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index f20c5f6d2..951ba7865 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -406,4 +406,14 @@ namespace MWWorld { return false; } + + int Class::getDoorState (const MWWorld::Ptr &ptr) const + { + throw std::runtime_error("this is not a door"); + } + + void Class::setDoorState (const MWWorld::Ptr &ptr, int state) const + { + throw std::runtime_error("this is not a door"); + } } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 27b842348..057bc906e 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -336,6 +336,11 @@ namespace MWWorld virtual int getBaseGold(const MWWorld::Ptr& ptr) const; virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; + + /// 0 = nothing, 1 = opening, 2 = closing + virtual int getDoorState (const MWWorld::Ptr &ptr) const; + /// This does not actually cause the door to move. Use World::activateDoor instead. + virtual void setDoorState (const MWWorld::Ptr &ptr, int state) const; }; } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index b18bda5e3..7127c92f3 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -599,7 +599,7 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i]; float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; magnitude *= params.mMultiplier; - visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), "", magnitude); + visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), -1, magnitude); ++i; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 7af926dca..4b92a920b 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -507,4 +507,24 @@ namespace MWWorld } return false; } + + Ptr Scene::searchPtrViaHandle (const std::string& handle) + { + for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); + iter!=mActiveCells.end(); ++iter) + if (Ptr ptr = (*iter)->searchViaHandle (handle)) + return ptr; + + return Ptr(); + } + + Ptr Scene::searchPtrViaActorId (int actorId) + { + for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); + iter!=mActiveCells.end(); ++iter) + if (Ptr ptr = (*iter)->searchViaActorId (actorId)) + return ptr; + + return Ptr(); + } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 8d8765378..449644754 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -104,6 +104,10 @@ namespace MWWorld ///< Remove an object from the scene, but not from the world model. bool isCellActive(const CellStore &cell); + + Ptr searchPtrViaHandle (const std::string& handle); + + Ptr searchPtrViaActorId (int actorId); }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index a3b9e64d3..2b1cbe4c7 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -275,7 +275,8 @@ namespace MWWorld +mStore.countSavedGameRecords() +mGlobalVariables.countSavedGameRecords() +1 // player record - +1; // weather record + +1 // weather record + +1; // actorId counter } void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const @@ -288,6 +289,9 @@ namespace MWWorld mRendering->writeFog(cellstore); } + MWMechanics::CreatureStats::writeActorIdCounter(writer); + progress.increaseProgress(); + mCells.write (writer, progress); mStore.write (writer, progress); mGlobalVariables.write (writer, progress); @@ -298,6 +302,12 @@ namespace MWWorld void World::readRecord (ESM::ESMReader& reader, int32_t type, const std::map& contentFileMap) { + if (type == ESM::REC_ACTC) + { + MWMechanics::CreatureStats::readActorIdCounter(reader); + return; + } + if (!mStore.readRecord (reader, type) && !mGlobalVariables.readRecord (reader, type) && !mPlayer->readRecord (reader, type) && @@ -547,17 +557,17 @@ namespace MWWorld { if (mPlayer->getPlayer().getRefData().getHandle()==handle) return mPlayer->getPlayer(); - for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); - iter!=mWorldScene->getActiveCells().end(); ++iter) - { - CellStore* cellstore = *iter; - Ptr ptr = cellstore->searchViaHandle (handle); - if (!ptr.isEmpty()) - return ptr; - } + return mWorldScene->searchPtrViaHandle (handle); + } - return MWWorld::Ptr(); + Ptr World::searchPtrViaActorId (int actorId) + { + // The player is not registered in any CellStore so must be checked manually + if (actorId == getPlayerPtr().getClass().getCreatureStats(getPlayerPtr()).getActorId()) + return getPlayerPtr(); + // Now search cells + return mWorldScene->searchPtrViaActorId (actorId); } void World::addContainerScripts(const Ptr& reference, CellStore * cell) @@ -1196,36 +1206,48 @@ namespace MWWorld while (it != mDoorStates.end()) { if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode()) + { + // The door is no longer in an active cell, or it was disabled. + // Erase from mDoorStates, since we no longer need to move it. + // Once we load the door's cell again (or re-enable the door), Door::insertObject will reinsert to mDoorStates. mDoorStates.erase(it++); + } else { float oldRot = Ogre::Radian(it->first.getRefData().getLocalRotation().rot[2]).valueDegrees(); float diff = duration * 90; - float targetRot = std::min(std::max(0.f, oldRot + diff * (it->second ? 1 : -1)), 90.f); + float targetRot = std::min(std::max(0.f, oldRot + diff * (it->second == 1 ? 1 : -1)), 90.f); localRotateObject(it->first, 0, 0, targetRot); + bool reached = (targetRot == 90.f && it->second) || targetRot == 0.f; + /// \todo should use convexSweepTest here std::vector collisions = mPhysics->getCollisions(it->first); for (std::vector::iterator cit = collisions.begin(); cit != collisions.end(); ++cit) { MWWorld::Ptr ptr = getPtrViaHandle(*cit); - if (MWWorld::Class::get(ptr).isActor()) + if (ptr.getClass().isActor()) { // Collided with actor, ask actor to try to avoid door if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr() ) { - MWMechanics::AiSequence& seq = MWWorld::Class::get(ptr).getCreatureStats(ptr).getAiSequence(); + MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) //Only add it once seq.stack(MWMechanics::AiAvoidDoor(it->first),ptr); } // we need to undo the rotation localRotateObject(it->first, 0, 0, oldRot); + reached = false; //break; //Removed in case multiple actors are touching } } - if ((targetRot == 90.f && it->second) || targetRot == 0.f) + if (reached) + { + // Mark as non-moving + it->first.getClass().setDoorState(it->first, 0); mDoorStates.erase(it++); + } else ++it; } @@ -1843,31 +1865,32 @@ namespace MWWorld void World::activateDoor(const MWWorld::Ptr& door) { - if (mDoorStates.find(door) != mDoorStates.end()) - { - // if currently opening, then close, if closing, then open - mDoorStates[door] = !mDoorStates[door]; - } - else + int state = door.getClass().getDoorState(door); + switch (state) { + case 0: if (door.getRefData().getLocalRotation().rot[2] == 0) - mDoorStates[door] = 1; // open + state = 1; // if closed, then open else - mDoorStates[door] = 0; // close + state = 2; // if open, then close + break; + case 2: + state = 1; // if closing, then open + break; + case 1: + default: + state = 2; // if opening, then close + break; } + door.getClass().setDoorState(door, state); + mDoorStates[door] = state; } - bool World::getOpenOrCloseDoor(const Ptr &door) + void World::activateDoor(const Ptr &door, bool open) { - if (mDoorStates.find(door) != mDoorStates.end()) - return !mDoorStates[door]; // if currently opening or closing, then do the opposite - return door.getRefData().getLocalRotation().rot[2] == 0; - } - - bool World::getIsMovingDoor(const Ptr& door) - { - bool result = mDoorStates.find(door) != mDoorStates.end(); - return result; + int state = open ? 1 : 2; + door.getClass().setDoorState(door, state); + mDoorStates[door] = state; } bool World::getPlayerStandingOn (const MWWorld::Ptr& object) @@ -2231,7 +2254,7 @@ namespace MWWorld const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) { ProjectileState state; - state.mActorHandle = actor.getRefData().getHandle(); + state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); state.mBow = bow; state.mVelocity = orient.yAxis() * speed; @@ -2318,7 +2341,7 @@ namespace MWWorld MagicBoltState state; state.mSourceName = sourceName; state.mId = id; - state.mActorHandle = actor.getRefData().getHandle(); + state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); state.mSpeed = speed; state.mStack = stack; @@ -2382,7 +2405,7 @@ namespace MWWorld if (obstacle == ptr) continue; - MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); + MWWorld::Ptr caster = searchPtrViaActorId(it->second.mActorId); // Arrow intersects with player immediately after shooting :/ if (obstacle == caster) @@ -2468,7 +2491,7 @@ namespace MWWorld if (obstacle == ptr) continue; - MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); + MWWorld::Ptr caster = searchPtrViaActorId(it->second.mActorId); if (caster.isEmpty()) caster = obstacle; @@ -2491,7 +2514,7 @@ namespace MWWorld if (explode) { - MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); + MWWorld::Ptr caster = searchPtrViaActorId(it->second.mActorId); explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName); deleteObject(ptr); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index d111d0008..7b85c3ed8 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -88,7 +88,7 @@ namespace MWWorld float mFacedDistance; std::map mDoorStates; - ///< only holds doors that are currently moving. 0 means closing, 1 opening + ///< only holds doors that are currently moving. 1 = opening, 2 = closing struct MagicBoltState { @@ -96,7 +96,7 @@ namespace MWWorld std::string mId; // Actor who casted this projectile - std::string mActorHandle; + int mActorId; // Name of item to display as effect source in magic menu (in case we casted an enchantment) std::string mSourceName; @@ -111,7 +111,7 @@ namespace MWWorld struct ProjectileState { // Actor who shot this projectile - std::string mActorHandle; + int mActorId; MWWorld::Ptr mBow; // bow or crossbow the projectile was fired from @@ -287,6 +287,9 @@ namespace MWWorld virtual Ptr searchPtrViaHandle (const std::string& handle); ///< Return a pointer to a liveCellRef with the given Ogre handle or Ptr() if not found + virtual Ptr searchPtrViaActorId (int actorId); + ///< Search is limited to the active cells. + virtual void adjustPosition (const Ptr& ptr); ///< Adjust position after load to be on ground. Must be called after model load. @@ -493,13 +496,11 @@ namespace MWWorld virtual void setupPlayer(); virtual void renderPlayer(); - /// if activated, should this door be opened or closed? - virtual bool getOpenOrCloseDoor(const MWWorld::Ptr& door); - - /// activate (open or close) an non-teleport door + /// open or close a non-teleport door (depending on current state) virtual void activateDoor(const MWWorld::Ptr& door); - virtual bool getIsMovingDoor(const MWWorld::Ptr& door); + /// open or close a non-teleport door as specified + virtual void activateDoor(const MWWorld::Ptr& door, bool open); virtual bool getPlayerStandingOn (const MWWorld::Ptr& object); ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object); ///< @return true if any actor is standing on \a object diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 533288048..d7bdaf36c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -45,7 +45,7 @@ add_component_dir (esm loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate dialoguestate statstate - npcstats creaturestats weatherstate quickkeys fogstate spellstate + npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate ) add_component_dir (misc diff --git a/components/esm/activespells.cpp b/components/esm/activespells.cpp new file mode 100644 index 000000000..4f51a619e --- /dev/null +++ b/components/esm/activespells.cpp @@ -0,0 +1,56 @@ +#include "activespells.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace ESM +{ + + void ActiveSpells::save(ESMWriter &esm) const + { + for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + esm.writeHNString ("ID__", it->first); + + const ActiveSpellParams& params = it->second; + + esm.writeHNT ("CAST", params.mCasterActorId); + esm.writeHNString ("DISP", params.mDisplayName); + esm.writeHNT ("TIME", params.mTimeStamp); + + for (std::vector::const_iterator effectIt = params.mEffects.begin(); effectIt != params.mEffects.end(); ++effectIt) + { + esm.writeHNT ("MGEF", effectIt->mEffectId); + if (effectIt->mArg != -1) + esm.writeHNT ("ARG_", effectIt->mArg); + esm.writeHNT ("MAGN", effectIt->mMagnitude); + esm.writeHNT ("DURA", effectIt->mDuration); + } + } + } + + void ActiveSpells::load(ESMReader &esm) + { + while (esm.isNextSub("ID__")) + { + std::string spellId = esm.getHString(); + + ActiveSpellParams params; + esm.getHNT (params.mCasterActorId, "CAST"); + params.mDisplayName = esm.getHNString ("DISP"); + esm.getHNT (params.mTimeStamp, "TIME"); + + while (esm.isNextSub("MGEF")) + { + ActiveEffect effect; + esm.getHT(effect.mEffectId); + effect.mArg = -1; + esm.getHNOT(effect.mArg, "ARG_"); + esm.getHNT (effect.mMagnitude, "MAGN"); + esm.getHNT (effect.mDuration, "DURA"); + params.mEffects.push_back(effect); + } + mSpells.insert(std::make_pair(spellId, params)); + } + } +} diff --git a/components/esm/activespells.hpp b/components/esm/activespells.hpp new file mode 100644 index 000000000..d9e9a8c63 --- /dev/null +++ b/components/esm/activespells.hpp @@ -0,0 +1,45 @@ +#ifndef OPENMW_ESM_ACTIVESPELLS_H +#define OPENMW_ESM_ACTIVESPELLS_H + +#include "effectlist.hpp" +#include "defs.hpp" + +#include +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // Parameters of an effect concerning lasting effects. + // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc. + // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster. + struct ActiveEffect + { + int mEffectId; + float mMagnitude; + int mArg; // skill or attribute + float mDuration; + }; + + // format 0, saved games only + struct ActiveSpells + { + struct ActiveSpellParams + { + std::vector mEffects; + ESM::TimeStamp mTimeStamp; + std::string mDisplayName; + int mCasterActorId; + }; + + typedef std::multimap TContainer; + TContainer mSpells; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/creaturelevliststate.cpp b/components/esm/creaturelevliststate.cpp new file mode 100644 index 000000000..164dae96e --- /dev/null +++ b/components/esm/creaturelevliststate.cpp @@ -0,0 +1,25 @@ +#include "creaturelevliststate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace ESM +{ + + void CreatureLevListState::load(ESMReader &esm) + { + ObjectState::load(esm); + + mSpawnActorId = -1; + esm.getHNOT (mSpawnActorId, "SPAW"); + } + + void CreatureLevListState::save(ESMWriter &esm, bool inInventory) const + { + ObjectState::save(esm, inInventory); + + if (mSpawnActorId != -1) + esm.writeHNT ("SPAW", mSpawnActorId); + } + +} diff --git a/components/esm/creaturelevliststate.hpp b/components/esm/creaturelevliststate.hpp new file mode 100644 index 000000000..99b5a7fa2 --- /dev/null +++ b/components/esm/creaturelevliststate.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_ESM_CREATURELEVLISTSTATE_H +#define OPENMW_ESM_CREATURELEVLISTSTATE_H + +#include "objectstate.hpp" + +namespace ESM +{ + // format 0, saved games only + + struct CreatureLevListState : public ObjectState + { + int mSpawnActorId; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + }; +} + +#endif diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 5bb6de662..66d848124 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -1,4 +1,3 @@ - #include "creaturestats.hpp" void ESM::CreatureStats::load (ESMReader &esm) @@ -72,7 +71,11 @@ void ESM::CreatureStats::load (ESMReader &esm) mLevel = 1; esm.getHNOT (mLevel, "LEVL"); + mActorId = -1; + esm.getHNOT (mActorId, "ACID"); + mSpells.load(esm); + mActiveSpells.load(esm); } void ESM::CreatureStats::save (ESMWriter &esm) const @@ -146,5 +149,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mLevel != 1) esm.writeHNT ("LEVL", mLevel); + if (mActorId != -1) + esm.writeHNT ("ACID", mActorId); + mSpells.save(esm); + mActiveSpells.save(esm); } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 2cf2c5b4f..7814d937a 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -10,6 +10,7 @@ #include "defs.hpp" #include "spellstate.hpp" +#include "activespells.hpp" namespace ESM { @@ -24,6 +25,7 @@ namespace ESM ESM::TimeStamp mTradeTime; int mGoldPool; + int mActorId; bool mDead; bool mDied; @@ -48,6 +50,7 @@ namespace ESM int mLevel; SpellState mSpells; + ActiveSpells mActiveSpells; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 9ef4dabd2..6c388de08 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -108,6 +108,7 @@ enum RecNameInts REC_KEYS = FourCC<'K','E','Y','S'>::value, REC_DYNA = FourCC<'D','Y','N','A'>::value, REC_ASPL = FourCC<'A','S','P','L'>::value, + REC_ACTC = FourCC<'A','C','T','C'>::value, // format 1 REC_FILT = 0x544C4946 diff --git a/components/esm/doorstate.cpp b/components/esm/doorstate.cpp new file mode 100644 index 000000000..0487be7a4 --- /dev/null +++ b/components/esm/doorstate.cpp @@ -0,0 +1,25 @@ +#include "doorstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace ESM +{ + + void DoorState::load(ESMReader &esm) + { + ObjectState::load(esm); + + mDoorState = 0; + esm.getHNOT (mDoorState, "ANIM"); + } + + void DoorState::save(ESMWriter &esm, bool inInventory) const + { + ObjectState::save(esm, inInventory); + + if (mDoorState != 0) + esm.writeHNT ("ANIM", mDoorState); + } + +} diff --git a/components/esm/doorstate.hpp b/components/esm/doorstate.hpp new file mode 100644 index 000000000..0df30afb0 --- /dev/null +++ b/components/esm/doorstate.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_ESM_DOORSTATE_H +#define OPENMW_ESM_DOORSTATE_H + +#include "objectstate.hpp" + +namespace ESM +{ + // format 0, saved games only + + struct DoorState : public ObjectState + { + int mDoorState; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + }; +} + +#endif diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index 57faefdf4..ca4f42217 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -74,6 +74,18 @@ class ESMWriter endRecord(name); } +private: + // Prevent using writeHNT with strings. This already happened by accident and results in + // state being discarded without any error on writing or reading it. :( + // writeHNString and friends must be used instead. + void writeHNT(const std::string &name, std::string data) + { + } + void writeT(const std::string& data) + { + } +public: + template void writeHNT(const std::string& name, const T& data, int size) { diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index 21f573bc7..3fa954182 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -140,7 +140,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const for (std::vector::const_iterator iter (mUsedIds.begin()); iter!=mUsedIds.end(); ++iter) - esm.writeHNT ("USED", *iter); + esm.writeHNString ("USED", *iter); if (mTimeToStartDrowning) esm.writeHNT ("DRTI", mTimeToStartDrowning); diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 2b44d0f4e..9eed2c7d9 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -150,7 +150,7 @@ device = master volume = 1.0 sfx volume = 1.0 music volume = 0.4 -footsteps volume = 0.6 +footsteps volume = 0.15 voice volume = 1.0 diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index c10892e52..6646ce273 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -18,6 +18,7 @@ namespace Physic PhysicActor::PhysicActor(const std::string &name, const std::string &mesh, PhysicEngine *engine, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, float scale) : mName(name), mEngine(engine), mMesh(mesh), mBoxScaledTranslation(0,0,0), mBoxRotationInverse(0,0,0,0) , mBody(0), mRaycastingBody(0), mOnGround(false), mCollisionMode(true), mBoxRotation(0,0,0,0) + , mCollisionBody(true) , mForce(0.0f) { mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation); @@ -49,8 +50,9 @@ namespace Physic void PhysicActor::enableCollisionBody(bool collision) { assert(mBody); - if(collision && !mCollisionMode) enableCollisionBody(); - if(!collision && mCollisionMode) disableCollisionBody(); + if(collision && !mCollisionBody) enableCollisionBody(); + if(!collision && mCollisionBody) disableCollisionBody(); + mCollisionBody = collision; } void PhysicActor::setPosition(const Ogre::Vector3 &pos) @@ -108,7 +110,7 @@ namespace Physic //Create the newly scaled rigid body mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, pos, rot); mRaycastingBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, pos, rot, 0, 0, true); - mEngine->addRigidBody(mBody, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map + mEngine->addRigidBody(mCollisionBody ? mBody : 0, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map } Ogre::Vector3 PhysicActor::getHalfExtents() const diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 4e035446e..16eb45306 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -175,6 +175,7 @@ namespace Physic Ogre::Vector3 mForce; bool mOnGround; bool mCollisionMode; + bool mCollisionBody; std::string mMesh; std::string mName;