merge master, resolve conflicts

deque
mrcheko 11 years ago
commit aa5647b45e

@ -594,6 +594,7 @@ if (WIN32)
4706 # Assignment in conditional expression
4738 # Storing 32-bit float result in memory, possible loss of performance
4986 # Undocumented warning that occurs in the crtdbg.h file
4987 # nonstandard extension used (triggered by setjmp.h)
4996 # Function was declared deprecated
# cause by ogre extensivly
@ -610,7 +611,9 @@ if (WIN32)
4305 # Truncating value (double to float, for example)
4309 # Variable overflow, trying to store 128 in a signed char for example
4355 # Using 'this' in member initialization list
4505 # Unreferenced local function has been removed
4701 # Potentially uninitialized local variable used
4702 # Unreachable code
4800 # Boolean optimization warning, e.g. myBool = (myInt != 0) instead of myBool = myInt
)
@ -618,19 +621,31 @@ if (WIN32)
set(WARNINGS "${WARNINGS} /wd${d}")
endforeach(d)
set_target_properties(shiny PROPERTIES COMPILE_FLAGS ${WARNINGS})
set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS ${WARNINGS})
# boost::wave has a few issues with signed / unsigned conversions, so we suppress those here
set(SHINY_WARNINGS "${WARNINGS} /wd4245")
set_target_properties(shiny PROPERTIES COMPILE_FLAGS ${SHINY_WARNINGS})
# there's an unreferenced local variable in the ogre platform, suppress it
set(SHINY_OGRE_WARNINGS "${WARNINGS} /wd4101")
set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS ${SHINY_OGRE_WARNINGS})
set_target_properties(sdl4ogre PROPERTIES COMPILE_FLAGS ${WARNINGS})
set_target_properties(oics PROPERTIES COMPILE_FLAGS ${WARNINGS})
set_target_properties(components PROPERTIES COMPILE_FLAGS ${WARNINGS})
if (BUILD_LAUNCHER)
set_target_properties(omwlauncher PROPERTIES COMPILE_FLAGS ${WARNINGS})
endif (BUILD_LAUNCHER)
set_target_properties(openmw PROPERTIES COMPILE_FLAGS ${WARNINGS})
if (BUILD_BSATOOL)
if (BUILD_BSATOOL)
set_target_properties(bsatool PROPERTIES COMPILE_FLAGS ${WARNINGS})
endif (BUILD_BSATOOL)
endif (BUILD_BSATOOL)
if (BUILD_ESMTOOL)
set_target_properties(esmtool PROPERTIES COMPILE_FLAGS ${WARNINGS})
endif (BUILD_ESMTOOL)
if (BUILD_OPENCS)
set_target_properties(opencs PROPERTIES COMPILE_FLAGS ${WARNINGS})
endif (BUILD_OPENCS)
if (BUILD_MWINIIMPORTER)
set_target_properties(mwiniimport PROPERTIES COMPILE_FLAGS ${WARNINGS})
endif (BUILD_MWINIIMPORTER)
endif(MSVC)
# Same for MinGW

@ -111,13 +111,17 @@ void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::Univers
{
std::vector<std::pair<std::string, std::vector<std::string> > > filterSource;
for (std::vector<CSMWorld::UniversalId>::iterator it = types.begin(); it != types.end(); ++it)
for (std::vector<CSMWorld::UniversalId>::iterator it(types.begin()); it != types.end(); ++it)
{
std::pair<std::string, std::vector<std::string> > pair( //splited long line
std::make_pair(it->getId(), mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(it->getType()))));
std::make_pair(it->getId(), mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(it->getType()))));
filterSource.push_back(pair);
if(!pair.second.empty())
{
filterSource.push_back(pair);
}
}
mFilterBox->createFilterRequest(filterSource, action);
}

@ -79,7 +79,7 @@ void CSVWorld::VarTypeDelegateFactory::add (ESM::VarType type)
std::vector<std::string> enums =
CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_ValueType);
if (type<0 && type>=enums.size())
if (static_cast<size_t>(type) >= enums.size())
throw std::logic_error ("Unsupported variable type");
mValues.push_back (std::make_pair (type, QString::fromUtf8 (enums[type].c_str())));

@ -67,7 +67,7 @@ add_openmw_dir (mwclass
add_openmw_dir (mwmechanics
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor
aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
disease pickpocket levelledlist combat steering obstacle
)

@ -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,10 +393,10 @@ namespace MWBase
virtual void setupPlayer() = 0;
virtual void renderPlayer() = 0;
virtual bool getOpenOrCloseDoor(const MWWorld::Ptr& door) = 0;
///< if activated, should this door be opened or closed?
/// open or close a non-teleport door (depending on current state)
virtual void activateDoor(const MWWorld::Ptr& door) = 0;
///< activate (open or close) an non-teleport door
/// 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

@ -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);
}

@ -2,6 +2,7 @@
#include "creaturelevlist.hpp"
#include <components/esm/loadlevlist.hpp>
#include <components/esm/creaturelevliststate.hpp>
#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<CreatureLevListCustomData&> (*ptr.getRefData().getCustomData());
if (customData.mSpawnActorId != -1)
return; // TODO: handle respawning
MWWorld::LiveCellRef<ESM::CreatureLevList> *ref =
ptr.get<ESM::CreatureLevList>();
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<CreatureLevListCustomData> data (new CreatureLevListCustomData);
data->mSpawnActorId = -1;
MWWorld::LiveCellRef<ESM::CreatureLevList> *ref =
ptr.get<ESM::CreatureLevList>();
ptr.getRefData().setCustomData(data.release());
}
}
std::string id = MWMechanics::getLevelledItem(ref->mBase, true);
void CreatureLevList::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
const
{
const ESM::CreatureLevListState& state2 = dynamic_cast<const ESM::CreatureLevListState&> (state);
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);
}
ensureCustomData(ptr);
CreatureLevListCustomData& customData = dynamic_cast<CreatureLevListCustomData&> (*ptr.getRefData().getCustomData());
customData.mSpawnActorId = state2.mSpawnActorId;
}
ptr.getRefData().setCustomData(data.release());
}
void CreatureLevList::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
const
{
ESM::CreatureLevListState& state2 = dynamic_cast<ESM::CreatureLevListState&> (state);
ensureCustomData(ptr);
CreatureLevListCustomData& customData = dynamic_cast<CreatureLevListCustomData&> (*ptr.getRefData().getCustomData());
state2.mSpawnActorId = customData.mSpawnActorId;
}
}

@ -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.
};
}

@ -2,6 +2,7 @@
#include "door.hpp"
#include <components/esm/loaddoor.hpp>
#include <components/esm/doorstate.hpp>
#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<const DoorCustomData&>(*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<MWWorld::Action> 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<ESM::Door>().insert(*ref), &cell);
}
void Door::ensureCustomData(const MWWorld::Ptr &ptr) const
{
if (!ptr.getRefData().getCustomData())
{
std::auto_ptr<DoorCustomData> 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<const DoorCustomData&>(*ptr.getRefData().getCustomData());
return customData.mDoorState;
}
void Door::setDoorState (const MWWorld::Ptr &ptr, int state) const
{
ensureCustomData(ptr);
DoorCustomData& customData = dynamic_cast<DoorCustomData&>(*ptr.getRefData().getCustomData());
customData.mDoorState = state;
}
void Door::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const
{
ensureCustomData(ptr);
DoorCustomData& customData = dynamic_cast<DoorCustomData&>(*ptr.getRefData().getCustomData());
const ESM::DoorState& state2 = dynamic_cast<const ESM::DoorState&>(state);
customData.mDoorState = state2.mDoorState;
}
void Door::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const
{
ensureCustomData(ptr);
const DoorCustomData& customData = dynamic_cast<const DoorCustomData&>(*ptr.getRefData().getCustomData());
ESM::DoorState& state2 = dynamic_cast<ESM::DoorState&>(state);
state2.mDoorState = customData.mDoorState;
}
}

@ -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.
};
}

@ -357,9 +357,6 @@ namespace MWClass
data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), "",
MWBase::Environment::get().getWorld()->getStore());
// Relates to NPC gold reset delay
data->mNpcStats.setTradeTime(MWWorld::TimeStamp(0.0, 0));
data->mNpcStats.setGoldPool(gold);
// store
@ -391,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
@ -621,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);

@ -695,6 +695,8 @@ protected:
typedef TypesetBookImpl::Section Section;
typedef TypesetBookImpl::Line Line;
typedef TypesetBookImpl::Run Run;
bool mIsPageReset;
size_t mPage;
struct TextFormat : ISubWidget
{
@ -745,6 +747,23 @@ protected:
void destroyDrawItem() {};
};
void resetPage()
{
mIsPageReset = true;
mPage = 0;
}
void setPage(size_t page)
{
mIsPageReset = false;
mPage = page;
}
bool isPageDifferent(size_t page)
{
return mIsPageReset || (mPage != page);
}
public:
typedef TypesetBookImpl::StyleImpl Style;
@ -760,14 +779,13 @@ public:
boost::shared_ptr <TypesetBookImpl> mBook;
size_t mPage;
MyGUI::ILayerNode* mNode;
ActiveTextFormats mActiveTextFormats;
PageDisplay ()
{
mPage = -1;
resetPage ();
mViewTop = 0;
mViewBottom = 0;
mFocusItem = NULL;
@ -902,7 +920,7 @@ public:
createActiveFormats (newBook);
mBook = newBook;
mPage = newPage;
setPage (newPage);
if (newPage < mBook->mPages.size ())
{
@ -918,19 +936,19 @@ public:
else
{
mBook.reset ();
mPage = -1;
resetPage ();
mViewTop = 0;
mViewBottom = 0;
}
}
else
if (mBook && mPage != newPage)
if (mBook && isPageDifferent (newPage))
{
if (mNode != NULL)
for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
mNode->outOfDate(i->second->mRenderItem);
mPage = newPage;
setPage (newPage);
if (newPage < mBook->mPages.size ())
{

@ -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);

@ -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);
};

@ -25,7 +25,7 @@ namespace MWGui
ItemView* mItemView;
SortFilterItemModel* mSortModel;
CompanionItemModel* mModel;
size_t mSelectedItem;
int mSelectedItem;
DragAndDrop* mDragAndDrop;

@ -62,7 +62,7 @@ namespace MWGui
MWGui::ItemView* mItemView;
SortFilterItemModel* mSortModel;
ItemModel* mModel;
size_t mSelectedItem;
int mSelectedItem;
MyGUI::Button* mDisposeCorpseButton;
MyGUI::Button* mTakeButton;

@ -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)

@ -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();

@ -511,7 +511,7 @@ namespace MWGui
void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos)
{
mHistory->setPosition(0,-pos);
mHistory->setPosition(0, pos * -1);
}
void DialogueWindow::addResponse(const std::string &text, const std::string &title)

@ -233,6 +233,9 @@ namespace MWGui
MWBase::World* world = MWBase::Environment::get().getWorld();
MWBase::Environment::get().getWorld()->breakInvisibility(
MWBase::Environment::get().getWorld()->getPlayerPtr());
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition();
float mouseX = cursorPosition.left / float(viewSize.width);

@ -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()

@ -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();

@ -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);
}
@ -541,9 +535,11 @@ namespace MWGui
int count = object.getRefData().getCount();
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWBase::Environment::get().getWorld()->breakInvisibility(player);
// add to player inventory
// can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::Ptr newObject = *player.getClass().getContainerStore (player).add (object, object.getRefData().getCount(), player);
// remove from world
MWBase::Environment::get().getWorld()->deleteObject (object);

@ -52,7 +52,7 @@ namespace MWGui
DragAndDrop* mDragAndDrop;
bool mPreviewDirty;
size_t mSelectedItem;
int mSelectedItem;
MWWorld::Ptr mPtr;

@ -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)

@ -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);

@ -465,7 +465,18 @@ namespace MWGui
void MapWindow::cellExplored(int x, int y)
{
mGlobalMapRender->exploreCell(x,y);
mQueuedToExplore.push_back(std::make_pair(x,y));
}
void MapWindow::onFrame(float dt)
{
for (std::vector<CellId>::iterator it = mQueuedToExplore.begin(); it != mQueuedToExplore.end(); ++it)
{
mGlobalMapRender->exploreCell(it->first, it->second);
}
mQueuedToExplore.clear();
NoDrop::onFrame(dt);
}
void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)

@ -97,14 +97,17 @@ namespace MWGui
void renderGlobalMap(Loading::Listener* loadingListener);
void addVisitedLocation(const std::string& name, int x, int y); // adds the marker to the global map
// adds the marker to the global map
void addVisitedLocation(const std::string& name, int x, int y);
// reveals this cell's map on the global map
void cellExplored(int x, int y);
void setGlobalMapPlayerPosition (float worldX, float worldY);
virtual void open();
void onFrame(float dt) { NoDrop::onFrame(dt); }
void onFrame(float dt);
/// Clear all savegame-specific data
void clear();
@ -132,6 +135,10 @@ namespace MWGui
typedef std::pair<int, int> CellId;
std::vector<CellId> mMarkers;
// Cells that should be explored in the next frame (i.e. their map revealed on the global map)
// We can't do this immediately, because the map update is not immediate either (see mNeedMapUpdate in scene.cpp)
std::vector<CellId> mQueuedToExplore;
MyGUI::Button* mEventBoxGlobal;
MyGUI::Button* mEventBoxLocal;

@ -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;

@ -42,8 +42,10 @@ namespace MWGui
std::map <int, std::vector<MagicEffectInfo> > mEffectSources;
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);
};

@ -1326,9 +1326,6 @@ namespace MWGui
void WindowManager::updatePlayer()
{
unsetSelectedSpell();
unsetSelectedWeapon();
mInventoryWindow->updatePlayer();
}
@ -1425,6 +1422,14 @@ namespace MWGui
mQuickKeysMenu->write(writer);
progress.increaseProgress();
if (!mSelectedSpell.empty())
{
writer.startRecord(ESM::REC_ASPL);
writer.writeHNString("ID__", mSelectedSpell);
writer.endRecord(ESM::REC_ASPL);
progress.increaseProgress();
}
}
void WindowManager::readRecord(ESM::ESMReader &reader, int32_t type)
@ -1433,12 +1438,18 @@ namespace MWGui
mMap->readRecord(reader, type);
else if (type == ESM::REC_KEYS)
mQuickKeysMenu->readRecord(reader, type);
else if (type == ESM::REC_ASPL)
{
reader.getSubNameIs("ID__");
mSelectedSpell = reader.getHString();
}
}
int WindowManager::countSavedGameRecords() const
{
return 1 // Global map
+ 1; // QuickKeysMenu
+ 1 // QuickKeysMenu
+ (!mSelectedSpell.empty() ? 1 : 0);
}
bool WindowManager::isSavingAllowed() const

@ -53,9 +53,9 @@ namespace MWMechanics
{
const MWWorld::TimeStamp& start = iter->second.mTimeStamp;
const std::vector<Effect>& effects = iter->second.mEffects;
const std::vector<ActiveEffect>& effects = iter->second.mEffects;
for (std::vector<Effect>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
for (std::vector<ActiveEffect>::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<Effect>& effects = iterator->second.mEffects;
const std::vector<ActiveEffect>& effects = iterator->second.mEffects;
int duration = 0;
for (std::vector<Effect>::const_iterator iter (effects.begin());
for (std::vector<ActiveEffect>::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<Effect> effects,
const std::string &displayName, const std::string& casterHandle)
void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector<ActiveEffect> 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<Effect>::const_iterator effectIt = it->second.mEffects.begin();
for (std::vector<ActiveEffect>::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<Effect>::iterator effectIt = it->second.mEffects.begin();
for (std::vector<ActiveEffect>::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<Effect>::iterator effectIt = it->second.mEffects.begin();
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end();)
{
const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectIt->mKey.mId);
const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().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;
}
}
}

@ -10,6 +10,7 @@
#include "magiceffects.hpp"
#include <components/esm/defs.hpp>
#include <components/esm/activespells.hpp>
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<Effect> mEffects;
std::vector<ActiveEffect> 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<std::string, ActiveSpellParams > 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<Effect> effects,
const std::string& displayName, const std::string& casterHandle);
void addSpell (const std::string& id, bool stack, std::vector<ActiveEffect> 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

@ -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;
@ -545,28 +548,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<int>::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)
@ -792,7 +821,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();
@ -801,6 +830,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)
@ -886,9 +917,8 @@ namespace MWMechanics
if (MWBase::Environment::get().getMechanicsManager()->isAIActive())
{
// make guards and creatures fight each other
if (timerUpdateAITargets == 0 && !iter->first.getClass().isNpc() && !listGuards.empty())
if (timerUpdateAITargets == 0 && iter->first.getTypeName() == typeid(ESM::Creature).name() && !listGuards.empty())
{
//findNthClosest
sBasePoint = Ogre::Vector3(iter->first.getRefData().getPosition().pos);
listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest creature
@ -972,21 +1002,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());
}
// FIXME: see http://bugs.openmw.org/issues/869
MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false);
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())
{
@ -997,8 +1023,11 @@ 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);
if (cls.isEssential(iter->first))
MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}");
}

@ -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.

@ -19,83 +19,26 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const
return new AiActivate(*this);
}
bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
ESM::Position pos = actor.getRefData().getPosition();
Movement &movement = actor.getClass().getMovementSettings(actor);
const ESM::Cell *cell = actor.getCell()->getCell();
MWWorld::Ptr player = world->getPlayerPtr();
if(cell->mData.mX != player.getCell()->getCell()->mData.mX)
{
int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) >
sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
{
movement.mPosition[1] = 0;
return false;
}
}
if(cell->mData.mY != player.getCell()->getCell()->mData.mY)
{
int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) >
sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
{
movement.mPosition[1] = 0;
return false;
}
}
MWWorld::Ptr target = world->searchPtr(mObjectId,false);
if(target == MWWorld::Ptr()) return true;
ESM::Position targetPos = target.getRefData().getPosition();
bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY;
if(!mPathFinder.isPathConstructed() || cellChange)
{
mCellX = cell->mData.mX;
mCellY = cell->mData.mY;
ESM::Pathgrid::Point dest;
dest.mX = targetPos.pos[0];
dest.mY = targetPos.pos[1];
dest.mZ = targetPos.pos[2];
ESM::Pathgrid::Point start;
start.mX = pos.pos[0];
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
mPathFinder.buildPath(start, dest, actor.getCell(), true);
}
if((pos.pos[0]-targetPos.pos[0])*(pos.pos[0]-targetPos.pos[0])+
(pos.pos[1]-targetPos.pos[1])*(pos.pos[1]-targetPos.pos[1])+
(pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 200*200)
{
movement.mPosition[1] = 0;
MWWorld::Ptr target = world->getPtr(mObjectId,false);
MWWorld::Class::get(target).activate(target,actor).get()->execute(actor);
return true;
}
if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
{
movement.mPosition[1] = 0;
MWWorld::Ptr target = world->getPtr(mObjectId,false);
MWWorld::Class::get(target).activate(target,actor).get()->execute(actor);
return true;
}
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
zTurn(actor, Ogre::Degree(zAngle));
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
movement.mPosition[1] = 1;
{
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
if(target == MWWorld::Ptr())
return true; //Target doesn't exist
//Set the target desition from the actor
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200) { //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
return true;
}
else {
pathTo(actor, dest, duration); //Go to the destination
}
return false;
}

@ -7,21 +7,21 @@
#include "pathfinding.hpp"
namespace MWMechanics
{
{
/// \brief Causes actor to walk to activatable object and activate it
/** Will activate when close to object **/
class AiActivate : public AiPackage
{
public:
public:
/// Constructor
/** \param objectId Reference to object to activate **/
AiActivate(const std::string &objectId);
virtual AiActivate *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
private:
std::string mObjectId;
PathFinder mPathFinder;
int mCellX;
int mCellY;
};

@ -0,0 +1,90 @@
#include "aiavoiddoor.hpp"
#include <iostream>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp"
#include "creaturestats.hpp"
#include "movement.hpp"
#include "mechanicsmanagerimp.hpp"
#include <OgreMath.h>
#include "steering.hpp"
MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::Ptr& doorPtr)
: AiPackage(), mDoorPtr(doorPtr), mDuration(1), mAdjAngle(0)
{
}
bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor,float duration)
{
ESM::Position pos = actor.getRefData().getPosition();
if(mDuration == 1) //If it just started, get the actor position as the stuck detection thing
mLastPos = pos;
mDuration -= duration; //Update timer
if(mDuration < 0) {
float x = pos.pos[0] - mLastPos.pos[0];
float y = pos.pos[1] - mLastPos.pos[1];
float z = pos.pos[2] - mLastPos.pos[2];
int distance = x * x + y * y + z * z;
if(distance < 10 * 10) { //Got stuck, didn't move
if(mAdjAngle == 0) //Try going in various directions
mAdjAngle = 1.57079632679f; //pi/2
else if (mAdjAngle == 1.57079632679f)
mAdjAngle = -1.57079632679;
else
mAdjAngle = 0;
mDuration = 1; //reset timer
}
else //Not stuck
return true; // We have tried backing up for more than one second, we've probably cleared it
}
if (!mDoorPtr.getClass().getDoorState(mDoorPtr))
return true; //Door is no longer opening
ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door
float x = pos.pos[0] - tPos.pos[0];
float y = pos.pos[1] - tPos.pos[1];
float dirToDoor = std::atan2(x,y) + pos.rot[2] + mAdjAngle; //Calculates the direction to the door, relative to the direction of the NPC
// For example, if the NPC is directly facing the door this will be pi/2
// Make actor move away from the door
actor.getClass().getMovementSettings(actor).mPosition[1] = -1 * std::sin(dirToDoor); //I knew I'd use trig someday
actor.getClass().getMovementSettings(actor).mPosition[0] = -1 * std::cos(dirToDoor);
//Make all nearby actors also avoid the door
std::vector<MWWorld::Ptr> actors;
MWBase::Environment::get().getMechanicsManager()->getActorsInRange(Ogre::Vector3(pos.pos[0],pos.pos[1],pos.pos[2]),100,actors);
for(std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); it++) {
if(*it != MWBase::Environment::get().getWorld()->getPlayerPtr()) { //Not the player
MWMechanics::AiSequence& seq = MWWorld::Class::get(*it).getCreatureStats(*it).getAiSequence();
if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) { //Only add it once
seq.stack(MWMechanics::AiAvoidDoor(mDoorPtr),*it);
}
}
}
return false;
}
std::string MWMechanics::AiAvoidDoor::getAvoidedDoor()
{
return mDoorPtr.getCellRef().mRefID;
}
MWMechanics::AiAvoidDoor *MWMechanics::AiAvoidDoor::clone() const
{
return new AiAvoidDoor(*this);
}
int MWMechanics::AiAvoidDoor::getTypeId() const
{
return TypeIdAvoidDoor;
}

@ -0,0 +1,38 @@
#ifndef GAME_MWMECHANICS_AIAVOIDDOOR_H
#define GAME_MWMECHANICS_AIAVOIDDOOR_H
#include "aipackage.hpp"
#include <string>
#include "pathfinding.hpp"
#include <components/esm/defs.hpp>
#include "../mwworld/class.hpp"
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
{
public:
/// Avoid door until the door is fully open
AiAvoidDoor(const MWWorld::Ptr& doorPtr);
virtual AiAvoidDoor *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
virtual int getTypeId() const;
/// Returns the door being avoided
std::string getAvoidedDoor();
private:
float mDuration;
MWWorld::Ptr mDoorPtr;
ESM::Position mLastPos;
float mAdjAngle;
};
}
#endif

@ -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),
@ -149,8 +149,11 @@ namespace MWMechanics
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
{
//General description
if(actor.getClass().getCreatureStats(actor).isDead()
|| mTarget.getClass().getCreatureStats(mTarget).isDead() )
if(actor.getClass().getCreatureStats(actor).isDead()) return true;
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
if(target.getClass().getCreatureStats(target).isDead())
return true;
//Update every frame
@ -322,7 +325,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;
@ -393,7 +396,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;
@ -429,7 +432,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]);
@ -473,9 +476,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;
@ -567,9 +570,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;
@ -624,9 +627,9 @@ namespace MWMechanics
return 1;
}
const MWWorld::Ptr &AiCombat::getTarget() const
MWWorld::Ptr AiCombat::getTarget() const
{
return mTarget;
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
}

@ -14,21 +14,24 @@
namespace MWMechanics
{
/// \brief Causes the actor to fight another actor
class AiCombat : public AiPackage
{
public:
///Constructor
/** \param actor Actor to fight **/
AiCombat(const MWWorld::Ptr& actor);
virtual AiCombat *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
virtual unsigned int getPriority() const;
const MWWorld::Ptr &getTarget() const;
///Returns target ID
MWWorld::Ptr getTarget() const;
private:
PathFinder mPathFinder;
@ -50,7 +53,7 @@ namespace MWMechanics
ESM::Position mLastPos;
MWMechanics::Movement mMovement;
MWWorld::Ptr mTarget;
int mTargetActorId;
const MWWorld::CellStore* mCell;
ObstacleCheck mObstacleCheck;
@ -60,7 +63,7 @@ namespace MWMechanics
MWWorld::CellRefList<ESM::Door>::List::iterator mDoorIter;
MWWorld::CellRefList<ESM::Door>& mDoors;
void buildNewPath(const MWWorld::Ptr& actor);
void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
};
}

@ -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<int>::max())
, mCellY(std::numeric_limits<int>::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<int>::max())
, mCellY(std::numeric_limits<int>::max())
{
@ -75,59 +77,14 @@ namespace MWMechanics
return true;
}
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
ESM::Position pos = actor.getRefData().getPosition();
bool cellChange = actor.getCell()->getCell()->mData.mX != mCellX || actor.getCell()->getCell()->mData.mY != mCellY;
if(actor.getCell()->getCell()->mData.mX != player.getCell()->getCell()->mData.mX)
{
int sideX = PathFinder::sgn(actor.getCell()->getCell()->mData.mX - player.getCell()->getCell()->mData.mX);
// Check if actor is near the border of an inactive cell. If so, pause walking.
if(sideX * (pos.pos[0] - actor.getCell()->getCell()->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE /
2.0 - 200))
{
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
return false;
}
}
if(actor.getCell()->getCell()->mData.mY != player.getCell()->getCell()->mData.mY)
{
int sideY = PathFinder::sgn(actor.getCell()->getCell()->mData.mY - player.getCell()->getCell()->mData.mY);
// Check if actor is near the border of an inactive cell. If so, pause walking.
if(sideY*(pos.pos[1] - actor.getCell()->getCell()->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE /
2.0 - 200))
{
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
return false;
}
}
if(!mPathFinder.isPathConstructed() || cellChange)
{
mCellX = actor.getCell()->getCell()->mData.mX;
mCellY = actor.getCell()->getCell()->mData.mY;
ESM::Pathgrid::Point dest;
dest.mX = mX;
dest.mY = mY;
dest.mZ = mZ;
ESM::Pathgrid::Point start;
start.mX = pos.pos[0];
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
mPathFinder.buildPath(start, dest, actor.getCell(), true);
}
if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]))
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 MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false);
const float* const leaderPos = actor.getRefData().getPosition().pos;
const float* const followerPos = follower.getRefData().getPosition().pos;
double differenceBetween[3];
@ -141,9 +98,8 @@ namespace MWMechanics
if(distanceBetweenResult <= mMaxDist * mMaxDist)
{
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
zTurn(actor, Ogre::Degree(zAngle));
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
if(pathTo(actor,ESM::Pathgrid::Point(mX,mY,mZ),duration)) //Returns true on path complete
return true;
mMaxDist = 470;
}
else

@ -8,23 +8,27 @@
namespace MWMechanics
{
/// \brief AI Package to have an NPC lead the player to a specific point
class AiEscort : public AiPackage
{
public:
AiEscort(const std::string &actorId,int duration, float x, float y, float z);
///< \implement AiEscort
AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z);
///< \implement AiEscortCell
/// 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 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 MWWorld::Ptr& actor,const std::string &cellId,int duration, float x, float y, float z);
virtual AiEscort *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
private:
std::string mActorId;
int mActorId;
std::string mCellId;
float mX;
float mY;

@ -11,30 +11,29 @@
#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(""), mTimer(0), mStuckTimer(0)
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), mTimer(0), mStuckTimer(0)
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(""), mTimer(0), mStuckTimer(0)
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
mTimer = mTimer + duration; //Update timer
mStuckTimer = mStuckTimer + duration; //Update stuck timer
mTotalTime = mTotalTime + duration; //Update total time following
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
if(!mAlwaysFollow) //Update if you only follow for a bit
@ -60,74 +59,18 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
}
//Set the target desition from the actor
ESM::Pathgrid::Point dest;
dest.mX = target.getRefData().getPosition().pos[0];
dest.mY = target.getRefData().getPosition().pos[1];
dest.mZ = target.getRefData().getPosition().pos[2];
//Current position, for pathfilding stuff
ESM::Pathgrid::Point start;
start.mX = pos.pos[0];
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
//Build the path to get to the destination
if(mPathFinder.getPath().empty())
mPathFinder.buildPath(start, dest, actor.getCell(), true);
//***********************
// Checks if you can't get to the end position at all
//***********************
if(mTimer > 0.25)
{
if(!mPathFinder.getPath().empty()) //Path has points in it
{
ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path
if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX)
+(dest.mY - lastPos.mY)*(dest.mY - lastPos.mY)
+(dest.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ)
> 100*100) //End of the path is far from the destination
mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go
}
mTimer = 0;
}
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
//************************
// Checks if you aren't moving; you're stuck
//************************
if(mStuckTimer>0.5) //Checks every half of a second
{
if((mStuckPos.pos[0] - pos.pos[0])*(mStuckPos.pos[0] - pos.pos[0])
+(mStuckPos.pos[1] - pos.pos[1])*(mStuckPos.pos[1] - pos.pos[1])
+(mStuckPos.pos[2] - pos.pos[2])*(mStuckPos.pos[2] - pos.pos[2]) < 100) //NPC is stuck
mPathFinder.buildPath(start, dest, actor.getCell(), true);
mStuckTimer = 0;
mStuckPos = pos;
}
//Checks if the path isn't over, turn tomards the direction that you're going
if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]))
{
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
}
if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2])
< 100*100)
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;
else
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
else {
pathTo(actor, dest, duration); //Go to the destination
}
//Check if you're far away
if((dest.mX - start.mX)*(dest.mX - start.mX)
+(dest.mY - start.mY)*(dest.mY - start.mY)
+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) > 1000*1000)
if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 1000)
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run
else if((dest.mX - start.mX)*(dest.mX - start.mX) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold
+(dest.mY - start.mY)*(dest.mY - start.mY)
+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) < 800*800)
else if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 800) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk
return false;
@ -135,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

@ -1,43 +1,45 @@
#ifndef GAME_MWMECHANICS_AIFALLOW_H
#define GAME_MWMECHANICS_AIFALLOW_H
#ifndef GAME_MWMECHANICS_AIFOLLOW_H
#define GAME_MWMECHANICS_AIFOLLOW_H
#include "aipackage.hpp"
#include <string>
#include "pathfinding.hpp"
#include "../../../components/esm/defs.hpp"
#include <components/esm/defs.hpp>
namespace MWMechanics
{
class AiFollow : public AiPackage
{
/// \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
{
public:
AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z);
AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z);
AiFollow(const std::string &ActorId);
/// Follow Actor for duration or until you arrive at a world position
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 MWWorld::Ptr& actor,const std::string &CellId,float duration, float X, float Y, float Z);
/// Follow Actor indefinitively
AiFollow(const MWWorld::Ptr& actor);
virtual AiFollow *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
/// Returns the actor being followed
std::string getFollowedActor();
private:
bool mAlwaysFollow; //this will make the actor always follow, thus ignoring mDuration and mX,mY,mZ (used for summoned creatures).
/// This will make the actor always follow.
/** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/
bool mAlwaysFollow;
float mDuration;
float mX;
float mY;
float mZ;
std::string mActorId;
std::string mCellId;
float mTimer;
float mStuckTimer;
float mTotalTime;
ESM::Position mStuckPos;
PathFinder mPathFinder;
};
}
#endif
int mActorId; // The actor we should follow
std::string mCellId;
};
}
#endif

@ -1,4 +1,131 @@
#include "aipackage.hpp"
#include <cmath>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp"
#include "creaturestats.hpp"
#include "movement.hpp"
#include "../mwworld/action.hpp"
#include <OgreMath.h>
#include "steering.hpp"
MWMechanics::AiPackage::~AiPackage() {}
MWMechanics::AiPackage::AiPackage() : mLastDoorChecked(MWWorld::Ptr()), mTimer(.26), mStuckTimer(0) { //mTimer starts at .26 to force initial pathbuild
}
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration)
{
//Update various Timers
mTimer += duration; //Update timer
mStuckTimer += duration; //Update stuck timer
mTotalTime += duration; //Update total time following
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
/// Stops the actor when it gets too close to a unloaded cell
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
const ESM::Cell *cell = actor.getCell()->getCell();
Movement &movement = actor.getClass().getMovementSettings(actor);
//Ensure pursuer doesn't leave loaded cells
if(cell->mData.mX != player.getCell()->getCell()->mData.mX)
{
int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
{
movement.mPosition[1] = 0;
return false;
}
}
if(cell->mData.mY != player.getCell()->getCell()->mData.mY)
{
int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
{
movement.mPosition[1] = 0;
return false;
}
}
}
//Start position
ESM::Pathgrid::Point start = pos.pos;
//***********************
/// Checks if you can't get to the end position at all, adds end position to end of path
/// Rebuilds path every quarter of a second, in case the target has moved
//***********************
if(mTimer > 0.25)
{
if(distance(mPrevDest, dest) > 10) { //Only rebuild path if it's moved
mPathFinder.buildPath(start, dest, actor.getCell(), true); //Rebuild path, in case the target has moved
mPrevDest = dest;
}
if(!mPathFinder.getPath().empty()) //Path has points in it
{
ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path
if(distance(dest, lastPos) > 100) //End of the path is far from the destination
mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go
}
mTimer = 0;
}
//************************
/// Checks if you aren't moving; attempts to unstick you
//************************
if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) //Path finished?
return true;
else if(mStuckTimer>0.5) //Every half second see if we need to take action to avoid something
{
/// TODO (tluppi#1#): Use ObstacleCheck here. Not working for some reason
//if(mObstacleCheck.check(actor, duration)) {
if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < 10 && distance(dest, start) > 20) { //Actually stuck, and far enough away from destination to care
// first check if we're walking into a door
MWWorld::Ptr door = getNearbyDoor(actor);
if(door != MWWorld::Ptr()) // NOTE: checks interior cells only
{
if(door.getCellRef().mTrap.empty() && mLastDoorChecked != door) { //Open the door if untrapped
door.getClass().activate(door, actor).get()->execute(actor);
mLastDoorChecked = door;
}
}
else // probably walking into another NPC
{
// TODO: diagonal should have same animation as walk forward
// but doesn't seem to do that?
actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f;
// change the angle a bit, too
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1])));
}
}
else { //Not stuck, so reset things
mStuckTimer = 0;
mStuckPos = pos;
mLastDoorChecked = MWWorld::Ptr(); //Resets it, in case he gets stuck behind the door again
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward
}
}
else {
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time
}
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
return false;
}

@ -1,6 +1,12 @@
#ifndef GAME_MWMECHANICS_AIPACKAGE_H
#define GAME_MWMECHANICS_AIPACKAGE_H
#include "pathfinding.hpp"
#include <components/esm/defs.hpp>
#include "../mwbase/world.hpp"
#include "obstacle.hpp"
namespace MWWorld
{
class Ptr;
@ -12,6 +18,7 @@ namespace MWMechanics
class AiPackage
{
public:
///Enumerates the various AITypes availible.
enum TypeId {
TypeIdNone = -1,
TypeIdWander = 0,
@ -20,21 +27,47 @@ namespace MWMechanics
TypeIdFollow = 3,
TypeIdActivate = 4,
TypeIdCombat = 5,
TypeIdPursue = 6
TypeIdPursue = 6,
TypeIdAvoidDoor = 7
};
///Default constructor
AiPackage();
///Default Deconstructor
virtual ~AiPackage();
///Clones the package
virtual AiPackage *clone() const = 0;
/// Updates and runs the package (Should run every frame)
/// \return Package completed?
virtual bool execute (const MWWorld::Ptr& actor,float duration) = 0;
///< \return Package completed?
/// Returns the TypeID of the AiPackage
/// \see enum TypeId
virtual int getTypeId() const = 0;
///< @see enum TypeId
/// Higher number is higher priority (0 being the lowest)
virtual unsigned int getPriority() const {return 0;}
///< higher number is higher priority (0 beeing the lowest)
protected:
/// Causes the actor to attempt to walk to the specified location
/** \return If the actor has arrived at his destination **/
bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration);
PathFinder mPathFinder;
ObstacleCheck mObstacleCheck;
float mDoorCheckDuration;
float mTimer;
float mStuckTimer;
float mTotalTime;
MWWorld::Ptr mLastDoorChecked; //Used to ensure we don't try to CONSTANTLY open a door
ESM::Position mStuckPos;
ESM::Pathgrid::Point mPrevDest;
};
}

@ -6,97 +6,55 @@
#include "../mwworld/action.hpp"
#include "../mwworld/cellstore.hpp"
#include "steering.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "movement.hpp"
#include "creaturestats.hpp"
MWMechanics::AiPursue::AiPursue(const MWWorld::Ptr target)
: mTarget(target)
namespace MWMechanics
{
AiPursue::AiPursue(const MWWorld::Ptr& actor)
: mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId())
{
}
MWMechanics::AiPursue *MWMechanics::AiPursue::clone() const
AiPursue *MWMechanics::AiPursue::clone() const
{
return new AiPursue(*this);
}
bool MWMechanics::AiPursue::execute (const MWWorld::Ptr& actor, float duration)
bool AiPursue::execute (const MWWorld::Ptr& actor, float duration)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
ESM::Position pos = actor.getRefData().getPosition();
Movement &movement = actor.getClass().getMovementSettings(actor);
const ESM::Cell *cell = actor.getCell()->getCell();
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
MWWorld::Ptr player = world->getPlayerPtr();
if(cell->mData.mX != player.getCell()->getCell()->mData.mX)
{
int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) >
sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
{
movement.mPosition[1] = 0;
return false;
}
}
if(cell->mData.mY != player.getCell()->getCell()->mData.mY)
{
int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) >
sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
{
movement.mPosition[1] = 0;
return false;
}
}
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); //The target to follow
// Big TODO: Sync this with current AiFollow. Move common code to a shared base class or helpers (applies to all AI packages, way too much duplicated code)
if(target == MWWorld::Ptr())
return true; //Target doesn't exist
ESM::Position targetPos = mTarget.getRefData().getPosition();
//Set the target desition from the actor
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY;
if(!mPathFinder.isPathConstructed() || cellChange || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
{
mCellX = cell->mData.mX;
mCellY = cell->mData.mY;
ESM::Pathgrid::Point dest;
dest.mX = targetPos.pos[0];
dest.mY = targetPos.pos[1];
dest.mZ = targetPos.pos[2];
ESM::Pathgrid::Point start;
start.mX = pos.pos[0];
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
mPathFinder.buildPath(start, dest, actor.getCell(), true);
}
if((pos.pos[0]-targetPos.pos[0])*(pos.pos[0]-targetPos.pos[0])+
(pos.pos[1]-targetPos.pos[1])*(pos.pos[1]-targetPos.pos[1])+
(pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 100*100)
{
movement.mPosition[1] = 0;
MWWorld::Class::get(mTarget).activate(mTarget,actor).get()->execute(actor);
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;
target.getClass().activate(target,actor).get()->execute(actor); //Arrest player
return true;
}
else {
pathTo(actor, dest, duration); //Go to the destination
}
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
zTurn(actor, Ogre::Degree(zAngle));
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
movement.mPosition[1] = 1;
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run
return false;
}
int MWMechanics::AiPursue::getTypeId() const
int AiPursue::getTypeId() const
{
return TypeIdPursue;
}
MWWorld::Ptr MWMechanics::AiPursue::getTarget() const
MWWorld::Ptr AiPursue::getTarget() const
{
return mTarget;
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
}
} // namespace MWMechanics

@ -9,23 +9,26 @@
namespace MWMechanics
{
/// \brief Makes the actor very closely follow the actor
/** Used for arresting players. Causes the actor to run to the pursued actor and activate them, to arrest them.
Note that while very similar to AiActivate, it will ONLY activate when evry close to target (Not also when the
path is completed). **/
class AiPursue : public AiPackage
{
public:
AiPursue(const MWWorld::Ptr target);
///Constructor
/** \param actor Actor to pursue **/
AiPursue(const MWWorld::Ptr& actor);
virtual AiPursue *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
virtual MWWorld::Ptr getTarget() const;
MWWorld::Ptr getTarget() const;
private:
MWWorld::Ptr mTarget;
PathFinder mPathFinder;
int mTargetActorId; // The actor to pursue
int mCellX;
int mCellY;
};

@ -17,21 +17,24 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
void MWMechanics::AiSequence::copy (const AiSequence& sequence)
namespace MWMechanics
{
void AiSequence::copy (const AiSequence& sequence)
{
for (std::list<AiPackage *>::const_iterator iter (sequence.mPackages.begin());
iter!=sequence.mPackages.end(); ++iter)
mPackages.push_back ((*iter)->clone());
}
MWMechanics::AiSequence::AiSequence() : mDone (false), mLastAiPackage(-1) {}
AiSequence::AiSequence() : mDone (false), mLastAiPackage(-1) {}
MWMechanics::AiSequence::AiSequence (const AiSequence& sequence) : mDone (false)
AiSequence::AiSequence (const AiSequence& sequence) : mDone (false)
{
copy (sequence);
}
MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& sequence)
AiSequence& AiSequence::operator= (const AiSequence& sequence)
{
if (this!=&sequence)
{
@ -43,12 +46,12 @@ MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& s
return *this;
}
MWMechanics::AiSequence::~AiSequence()
AiSequence::~AiSequence()
{
clear();
}
int MWMechanics::AiSequence::getTypeId() const
int AiSequence::getTypeId() const
{
if (mPackages.empty())
return -1;
@ -56,17 +59,19 @@ int MWMechanics::AiSequence::getTypeId() const
return mPackages.front()->getTypeId();
}
bool MWMechanics::AiSequence::getCombatTarget(std::string &targetActorId) const
bool AiSequence::getCombatTarget(std::string &targetActorId) const
{
if (getTypeId() != AiPackage::TypeIdCombat)
return false;
const AiCombat *combat = static_cast<const AiCombat *>(mPackages.front());
targetActorId = combat->getTarget().getRefData().getHandle();
MWWorld::Ptr target = combat->getTarget();
targetActorId = target.getClass().getCreatureStats(target).getActorId();
return true;
}
bool MWMechanics::AiSequence::canAddTarget(const ESM::Position& actorPos, float distToTarget) const
bool AiSequence::canAddTarget(const ESM::Position& actorPos, float distToTarget) const
{
bool firstCombatFound = false;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
@ -78,7 +83,7 @@ bool MWMechanics::AiSequence::canAddTarget(const ESM::Position& actorPos, float
firstCombatFound = true;
const AiCombat *combat = static_cast<const AiCombat *>(*it);
if (combat->getTarget() != player) return false; // only 1 non-player target allowed
if (combat->getTarget() != player ) return false; // only 1 non-player target allowed
else
{
// add new target only if current target (player) is farther
@ -93,7 +98,7 @@ bool MWMechanics::AiSequence::canAddTarget(const ESM::Position& actorPos, float
return true;
}
void MWMechanics::AiSequence::stopCombat()
void AiSequence::stopCombat()
{
while (getTypeId() == AiPackage::TypeIdCombat)
{
@ -102,7 +107,7 @@ void MWMechanics::AiSequence::stopCombat()
}
}
void MWMechanics::AiSequence::stopPursuit()
void AiSequence::stopPursuit()
{
while (getTypeId() == AiPackage::TypeIdPursue)
{
@ -111,12 +116,12 @@ void MWMechanics::AiSequence::stopPursuit()
}
}
bool MWMechanics::AiSequence::isPackageDone() const
bool AiSequence::isPackageDone() const
{
return mDone;
}
void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration)
void AiSequence::execute (const MWWorld::Ptr& actor,float duration)
{
if(actor != MWBase::Environment::get().getWorld()->getPlayerPtr())
{
@ -176,7 +181,7 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration)
}
}
void MWMechanics::AiSequence::clear()
void AiSequence::clear()
{
for (std::list<AiPackage *>::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter)
delete *iter;
@ -184,7 +189,7 @@ void MWMechanics::AiSequence::clear()
mPackages.clear();
}
void MWMechanics::AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor)
void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor)
{
if (package.getTypeId() == AiPackage::TypeIdCombat || package.getTypeId() == AiPackage::TypeIdPursue)
{
@ -214,12 +219,12 @@ void MWMechanics::AiSequence::stack (const AiPackage& package, const MWWorld::Pt
mPackages.push_front (package.clone());
}
void MWMechanics::AiSequence::queue (const AiPackage& package)
void AiSequence::queue (const AiPackage& package)
{
mPackages.push_back (package.clone());
}
MWMechanics::AiPackage* MWMechanics::AiSequence::getActivePackage()
AiPackage* MWMechanics::AiSequence::getActivePackage()
{
if(mPackages.empty())
throw std::runtime_error(std::string("No AI Package!"));
@ -227,7 +232,7 @@ MWMechanics::AiPackage* MWMechanics::AiSequence::getActivePackage()
return mPackages.front();
}
void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list)
void AiSequence::fill(const ESM::AIPackageList &list)
{
for (std::vector<ESM::AIPackage>::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it)
{
@ -243,7 +248,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)
{
@ -258,8 +264,11 @@ 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);
}
}
} // namespace MWMechanics

@ -13,69 +13,82 @@ namespace MWWorld
namespace MWMechanics
{
class AiPackage;
/// \brief Sequence of AI-packages for a single actor
/** The top-most AI package is run each frame. When completed, it is removed from the stack. **/
class AiSequence
{
///AiPackages to run though
std::list<AiPackage *> mPackages;
///Finished with top AIPackage, set for one frame
bool mDone;
///Copy AiSequence
void copy (const AiSequence& sequence);
// The type of AI package that ran last
/// The type of AI package that ran last
int mLastAiPackage;
public:
///Default constructor
AiSequence();
/// Copy Constructor
AiSequence (const AiSequence& sequence);
/// Assignment operator
AiSequence& operator= (const AiSequence& sequence);
virtual ~AiSequence();
/// Returns currently executing AiPackage type
/** \see enum AiPackage::TypeId **/
int getTypeId() const;
///< @see enum AiPackage::TypeId
/// Get the typeid of the Ai package that ran last
/** NOT the currently "active" Ai package that will be run in the next frame.
This difference is important when an Ai package has just finished and been removed.
\see enum AiPackage::TypeId **/
int getLastRunTypeId() const { return mLastAiPackage; }
///< Get the typeid of the Ai package that ran last, NOT the currently "active" Ai package that will be run in the next frame.
/// This difference is important when an Ai package has just finished and been removed.
/// Return true and assign target if combat package is currently active, return false otherwise
bool getCombatTarget (std::string &targetActorId) const;
///< Return true and assign target if combat package is currently
/// active, return false otherwise
bool canAddTarget(const ESM::Position& actorPos, float distToTarget) const;
///< Function assumes that actor can have only 1 target apart player
/// Removes all combat packages until first non-combat or stack empty.
void stopCombat();
///< Removes all combat packages until first non-combat or stack empty.
void stopPursuit();
///< Removes all pursue packages until first non-pursue or stack empty.
/// Has a package been completed during the last update?
bool isPackageDone() const;
///< Has a package been completed during the last update?
/// Removes all pursue packages until first non-pursue or stack empty.
void stopPursuit();
/// Execute current package, switching if needed.
void execute (const MWWorld::Ptr& actor,float duration);
///< Execute package.
/// Remove all packages.
void clear();
///< Remove all packages.
///< Add \a package to the front of the sequence
/** Suspends current package
@param actor The actor that owns this AiSequence **/
void stack (const AiPackage& package, const MWWorld::Ptr& actor);
///< Add \a package to the front of the sequence (suspends current package)
/// @param actor The actor that owns this AiSequence
/// Add \a package to the end of the sequence
/** Executed after all other packages have been completed **/
void queue (const AiPackage& package);
///< Add \a package to the end of the sequence (executed after all other packages have been
/// completed)
/// Return the current active package.
/** If there is no active package, it will throw an exception **/
AiPackage* getActivePackage();
///< return the current active package. If there is no active package, throw an exeption
/// Fills the AiSequence with packages
/** Typically used for loading from the ESM
\see ESM::AIPackageList **/
void fill (const ESM::AIPackageList& list);
};
}

@ -6,15 +6,16 @@
#include "pathfinding.hpp"
namespace MWMechanics
{
{
/// \brief Causes the AI to travel to the specified point
class AiTravel : public AiPackage
{
public:
public:
/// Default constructor
AiTravel(float x, float y, float z);
virtual AiTravel *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;

@ -14,20 +14,27 @@
namespace MWMechanics
{
/// \brief Causes the Actor to wander within a specified range
class AiWander : public AiPackage
{
public:
/// Constructor
/** \param distance Max distance the ACtor will wander
\param duration Time, in hours, that this package will be preformed
\param timeOfDay Start time of the package, if it has a duration. Currently unimplemented
\param idle Chances of each idle to play (9 in total)
\param repeat Repeat wander or not **/
AiWander(int distance, int duration, int timeOfDay, const std::vector<int>& idle, bool repeat);
virtual AiPackage *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
///< 0: Wander
/// Set the position to return to for a stationary (non-wandering) actor
/** In case another AI package moved the actor elsewhere **/
void setReturnPosition (const Ogre::Vector3& position);
///< Set the position to return to for a stationary (non-wandering) actor, in case
/// another AI package moved the actor elsewhere
private:
void stopWalking(const MWWorld::Ptr& actor);

@ -431,6 +431,11 @@ void CharacterController::playRandomDeath(float startpoint)
mDeathState = static_cast<CharacterState>(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);
}
@ -1372,9 +1377,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;

@ -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; }

@ -146,8 +146,8 @@ namespace MWMechanics
|| weapon.get<ESM::Weapon>()->mBase->mData.mFlags & ESM::Weapon::Magical))
damage *= multiplier;
if (weapon.get<ESM::Weapon>()->mBase->mData.mFlags & ESM::Weapon::Silver
& actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
if ((weapon.get<ESM::Weapon>()->mBase->mData.mFlags & ESM::Weapon::Silver)
&& actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
damage *= MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWereWolfSilverWeaponDamageMult")->getFloat();
if (damage == 0 && attacker.getRefData().getHandle() == "player")

@ -11,14 +11,18 @@
namespace MWMechanics
{
int CreatureStats::sActorId = 0;
CreatureStats::CreatureStats()
: mLevel (0), mDead (false), mDied (false), mFriendlyHits (0),
mTalkedTo (false), mAlarmed (false),
mAttacked (false), mHostile (false),
mAttackingOrSpell(false),
mIsWerewolf(false),
mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false),
mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f)
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), mActorId(-1)
{
for (int i=0; i<4; ++i)
mAiSettings[i] = 0;
@ -348,20 +352,6 @@ namespace MWMechanics
return mLastHitObject;
}
bool CreatureStats::canUsePower(const std::string &power) const
{
std::map<std::string, MWWorld::TimeStamp>::const_iterator it = mUsedPowers.find(power);
if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp())
return true;
else
return false;
}
void CreatureStats::usePower(const std::string &power)
{
mUsedPowers[power] = MWBase::Environment::get().getWorld()->getTimeStamp();
}
void CreatureStats::addToFallHeight(float height)
{
mFallHeight += height;
@ -481,20 +471,79 @@ namespace MWMechanics
void CreatureStats::writeState (ESM::CreatureStats& state) const
{
for (int i=0; i<8; ++i)
for (int i=0; i<ESM::Attribute::Length; ++i)
mAttributes[i].writeState (state.mAttributes[i]);
for (int i=0; i<3; ++i)
mDynamic[i].writeState (state.mDynamic[i]);
state.mTradeTime = mTradeTime.toEsm();
state.mGoldPool = mGoldPool;
state.mDead = mDead;
state.mDied = mDied;
state.mFriendlyHits = mFriendlyHits;
state.mTalkedTo = mTalkedTo;
state.mAlarmed = mAlarmed;
state.mAttacked = mAttacked;
state.mHostile = mHostile;
state.mAttackingOrSpell = mAttackingOrSpell;
// TODO: rewrite. does this really need 3 separate bools?
state.mKnockdown = mKnockdown;
state.mKnockdownOneFrame = mKnockdownOneFrame;
state.mKnockdownOverOneFrame = mKnockdownOverOneFrame;
state.mHitRecovery = mHitRecovery;
state.mBlock = mBlock;
state.mMovementFlags = mMovementFlags;
state.mAttackStrength = mAttackStrength;
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)
{
for (int i=0; i<8; ++i)
for (int i=0; i<ESM::Attribute::Length; ++i)
mAttributes[i].readState (state.mAttributes[i]);
for (int i=0; i<3; ++i)
mDynamic[i].readState (state.mDynamic[i]);
mTradeTime = MWWorld::TimeStamp(state.mTradeTime);
mGoldPool = state.mGoldPool;
mFallHeight = state.mFallHeight;
mDead = state.mDead;
mDied = state.mDied;
mFriendlyHits = state.mFriendlyHits;
mTalkedTo = state.mTalkedTo;
mAlarmed = state.mAlarmed;
mAttacked = state.mAttacked;
mHostile = state.mHostile;
mAttackingOrSpell = state.mAttackingOrSpell;
// TODO: rewrite. does this really need 3 separate bools?
mKnockdown = state.mKnockdown;
mKnockdownOneFrame = state.mKnockdownOneFrame;
mKnockdownOverOneFrame = state.mKnockdownOverOneFrame;
mHitRecovery = state.mHitRecovery;
mBlock = state.mBlock;
mMovementFlags = state.mMovementFlags;
mAttackStrength = state.mAttackStrength;
mFallHeight = state.mFallHeight;
mLastHitObject = state.mLastHitObject;
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
@ -516,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");
}
}

@ -24,6 +24,7 @@ namespace MWMechanics
///
class CreatureStats
{
static int sActorId;
DrawState_ mDrawState;
AttributeValue mAttributes[8];
DynamicStat<float> mDynamic[3]; // health, magicka, fatigue
@ -55,15 +56,16 @@ namespace MWMechanics
// Do we need to recalculate stats derived from attributes or other factors?
bool mRecalcDynamicStats;
std::map<std::string, MWWorld::TimeStamp> mUsedPowers;
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.
bool mIsWerewolf;
AttributeValue mWerewolfAttributes[8];
int mLevel;
public:
@ -84,9 +86,6 @@ namespace MWMechanics
/// @return total fall height
float land();
bool canUsePower (const std::string& power) const;
void usePower (const std::string& power);
const AttributeValue & getAttribute(int index) const;
const DynamicStat<float> & getHealth() const;
@ -224,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<int> mBoundItems;
// Same as above
std::map<int, std::string> mSummonedCreatures;
// TODO: store in savegame
// TODO: encapsulate?
// <ESM::MagicEffect index, actor index>
std::map<int, int> mSummonedCreatures;
// Contains summoned creatures with an expired lifetime that have not been deleted yet.
std::vector<int> 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();
};
}

@ -6,9 +6,9 @@ namespace MWMechanics
/// \note The _ suffix is required to avoid a collision with a Windoze macro. Die, Microsoft! Die!
enum DrawState_
{
DrawState_Weapon = 0,
DrawState_Spell = 1,
DrawState_Nothing = 2
DrawState_Nothing = 0,
DrawState_Weapon = 1,
DrawState_Spell = 2
};
}

@ -70,7 +70,7 @@ namespace MWMechanics
return getLevelledItem(ref.getPtr().get<ESM::CreatureLevList>()->mBase, failChance);
}
}
catch (std::logic_error& e)
catch (std::logic_error&)
{
// Vanilla doesn't fail on nonexistent items in levelled lists
std::cerr << "Warning: ignoring nonexistent item '" << item << "'" << std::endl;

@ -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;
};

@ -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<std::string, int>& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks();
if (factions.find(Misc::StringUtils::lowerCase(faction)) == factions.end())
@ -342,7 +342,9 @@ namespace MWMechanics
MWWorld::ContainerStoreIterator enchantItem = inv.getSelectedEnchantItem();
if (enchantItem != inv.end())
winMgr->setSelectedEnchantItem(*enchantItem);
else if (winMgr->getSelectedSpell() == "")
else if (!winMgr->getSelectedSpell().empty())
winMgr->setSelectedSpell(winMgr->getSelectedSpell(), int(MWMechanics::getSpellSuccessChance(winMgr->getSelectedSpell(), mWatched)));
else
winMgr->unsetSelectedSpell();
}
@ -383,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);

@ -432,9 +432,9 @@ float MWMechanics::NpcStats::getTimeToStartDrowning() const
{
return mTimeToStartDrowning;
}
void MWMechanics::NpcStats::setTimeToStartDrowning(float time)
{
assert(time>=0 && time<=20);
mTimeToStartDrowning=time;
}
@ -446,11 +446,16 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const
state.mDisposition = mDisposition;
for (int i=0; i<27; ++i)
for (int i=0; i<ESM::Skill::Length; ++i)
{
mSkill[i].writeState (state.mSkills[i].mRegular);
mWerewolfSkill[i].writeState (state.mSkills[i].mWerewolf);
}
for (int i=0; i<ESM::Attribute::Length; ++i)
{
mWerewolfAttributes[i].writeState (state.mWerewolfAttributes[i]);
}
state.mIsWerewolf = mIsWerewolf;
state.mCrimeId = mCrimeId;
@ -467,10 +472,9 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const
state.mReputation = mReputation;
state.mWerewolfKills = mWerewolfKills;
state.mProfit = mProfit;
state.mAttackStrength = mAttackStrength;
state.mLevelProgress = mLevelProgress;
for (int i=0; i<8; ++i)
for (int i=0; i<ESM::Attribute::Length; ++i)
state.mSkillIncrease[i] = mSkillIncreases[i];
std::copy (mUsedIds.begin(), mUsedIds.end(), std::back_inserter (state.mUsedIds));
@ -500,21 +504,26 @@ void MWMechanics::NpcStats::readState (const ESM::NpcStats& state)
mDisposition = state.mDisposition;
for (int i=0; i<27; ++i)
for (int i=0; i<ESM::Skill::Length; ++i)
{
mSkill[i].readState (state.mSkills[i].mRegular);
mWerewolfSkill[i].readState (state.mSkills[i].mWerewolf);
}
for (int i=0; i<ESM::Attribute::Length; ++i)
{
mWerewolfAttributes[i].readState (state.mWerewolfAttributes[i]);
}
mIsWerewolf = state.mIsWerewolf;
mCrimeId = state.mCrimeId;
mBounty = state.mBounty;
mReputation = state.mReputation;
mWerewolfKills = state.mWerewolfKills;
mProfit = state.mProfit;
mAttackStrength = state.mAttackStrength;
mLevelProgress = state.mLevelProgress;
for (int i=0; i<8; ++i)
for (int i=0; i<ESM::Attribute::Length; ++i)
mSkillIncreases[i] = state.mSkillIncrease[i];
for (std::vector<std::string>::const_iterator iter (state.mUsedIds.begin());

@ -31,8 +31,8 @@ namespace MWMechanics
std::map<std::string, int> mFactionRank;
int mDisposition;
SkillValue mSkill[27];
SkillValue mWerewolfSkill[27];
SkillValue mSkill[ESM::Skill::Length];
SkillValue mWerewolfSkill[ESM::Skill::Length];
int mBounty;
std::set<std::string> mExpelled;
std::map<std::string, int> mFactionReputation;
@ -40,7 +40,6 @@ namespace MWMechanics
int mCrimeId;
int mWerewolfKills;
int mProfit;
float mAttackStrength;
int mLevelProgress; // 0-10

@ -19,11 +19,19 @@ namespace MWMechanics
// Limitation: there can be false detections, and does not test whether the
// actor is facing the door.
bool proximityToDoor(const MWWorld::Ptr& actor, float minSqr, bool closed)
{
if(getNearbyDoor(actor, minSqr, closed)!=MWWorld::Ptr())
return true;
else
return false;
}
MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minSqr, bool closed)
{
MWWorld::CellStore *cell = actor.getCell();
if(cell->getCell()->isExterior())
return false; // check interior cells only
return MWWorld::Ptr(); // check interior cells only
// Check all the doors in this cell
MWWorld::CellRefList<ESM::Door>& doors = cell->get<ESM::Door>();
@ -31,14 +39,14 @@ namespace MWMechanics
MWWorld::CellRefList<ESM::Door>::List::iterator it = refList.begin();
Ogre::Vector3 pos(actor.getRefData().getPosition().pos);
// TODO: How to check whether the actor is facing a door? Below code is for
// the player, perhaps it can be adapted.
/// TODO: How to check whether the actor is facing a door? Below code is for
/// the player, perhaps it can be adapted.
//MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject();
//if(!ptr.isEmpty())
//std::cout << "faced door " << ptr.getClass().getName(ptr) << std::endl;
// TODO: The in-game observation of rot[2] value seems to be the
// opposite of the code in World::activateDoor() ::confused::
/// TODO: The in-game observation of rot[2] value seems to be the
/// opposite of the code in World::activateDoor() ::confused::
for (; it != refList.end(); ++it)
{
MWWorld::LiveCellRef<ESM::Door>& ref = *it;
@ -46,10 +54,10 @@ namespace MWMechanics
if((closed && ref.mData.getLocalRotation().rot[2] == 0) ||
(!closed && ref.mData.getLocalRotation().rot[2] >= 1))
{
return true; // found, stop searching
return MWWorld::Ptr(&ref, actor.getCell()); // found, stop searching
}
}
return false; // none found
return MWWorld::Ptr(); // none found
}
ObstacleCheck::ObstacleCheck():

@ -1,6 +1,10 @@
#ifndef OPENMW_MECHANICS_OBSTACLE_H
#define OPENMW_MECHANICS_OBSTACLE_H
//#include "../mwbase/world.hpp"
//#include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp"
namespace MWWorld
{
class Ptr;
@ -8,14 +12,20 @@ namespace MWWorld
namespace MWMechanics
{
// NOTE: determined empirically based on in-game behaviour
/// NOTE: determined empirically based on in-game behaviour
static const float MIN_DIST_TO_DOOR_SQUARED = 128*128;
// tests actor's proximity to a closed door by default
/// tests actor's proximity to a closed door by default
bool proximityToDoor(const MWWorld::Ptr& actor,
float minSqr = MIN_DIST_TO_DOOR_SQUARED,
bool closed = true);
/// Returns door pointer within range. No guarentee is given as too which one
/** \return Pointer to the door, or NULL if none exists **/
MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor,
float minSqr = MIN_DIST_TO_DOOR_SQUARED,
bool closed = true);
class ObstacleCheck
{
public:

@ -11,30 +11,6 @@
namespace
{
float distanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z)
{
x -= point.mX;
y -= point.mY;
z -= point.mZ;
return sqrt(x * x + y * y + 0.1 * z * z);
}
float distance(ESM::Pathgrid::Point point, float x, float y, float z)
{
x -= point.mX;
y -= point.mY;
z -= point.mZ;
return sqrt(x * x + y * y + z * z);
}
float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b)
{
float x = a.mX - b.mX;
float y = a.mY - b.mY;
float z = a.mZ - b.mZ;
return sqrt(x * x + y * y + z * z);
}
// Slightly cheaper version for comparisons.
// Caller needs to be careful for very short distances (i.e. less than 1)
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
@ -114,6 +90,30 @@ namespace
namespace MWMechanics
{
float distanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z)
{
x -= point.mX;
y -= point.mY;
z -= point.mZ;
return sqrt(x * x + y * y + 0.1 * z * z);
}
float distance(ESM::Pathgrid::Point point, float x, float y, float z)
{
x -= point.mX;
y -= point.mY;
z -= point.mZ;
return sqrt(x * x + y * y + z * z);
}
float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b)
{
float x = a.mX - b.mX;
float y = a.mY - b.mY;
float z = a.mZ - b.mZ;
return sqrt(x * x + y * y + z * z);
}
PathFinder::PathFinder()
: mIsPathConstructed(false),
mPathgrid(NULL),

@ -13,6 +13,8 @@ namespace MWWorld
namespace MWMechanics
{
float distance(ESM::Pathgrid::Point point, float x, float y, float);
float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b);
class PathFinder
{
public:

@ -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<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
@ -219,10 +222,12 @@ namespace MWMechanics
}
ESM::EffectList reflectedEffects;
std::vector<ActiveSpells::Effect> appliedLastingEffects;
std::vector<ActiveSpells::ActiveEffect> appliedLastingEffects;
bool firstAppliedEffect = true;
bool anyHarmfulEffect = false;
bool castByPlayer = (!caster.isEmpty() && caster.getRefData().getHandle() == "player");
for (std::vector<ESM::ENAMstruct>::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<ActiveSpells::Effect> 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<ActiveSpells::ActiveEffect> 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<ESM::Static>().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
}

@ -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);
};

@ -4,6 +4,7 @@
#include <cstdlib>
#include <components/esm/loadspel.hpp>
#include <components/esm/spellstate.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -30,10 +31,19 @@ namespace MWMechanics
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
std::vector<float> random;
random.resize(spell->mEffects.mList.size());
for (unsigned int i=0; i<random.size();++i)
random[i] = static_cast<float> (std::rand()) / RAND_MAX;
std::map<const int, float> random;
// Determine the random magnitudes (unless this is a castable spell, in which case
// they will be determined when the spell is cast)
if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell)
{
for (unsigned int i=0; i<spell->mEffects.mList.size();++i)
{
if (spell->mEffects.mList[i].mMagnMin != spell->mEffects.mList[i].mMagnMax)
random[i] = static_cast<float> (std::rand()) / RAND_MAX;
}
}
mSpells.insert (std::make_pair (Misc::StringUtils::lowerCase(spellId), random));
}
}
@ -67,7 +77,11 @@ namespace MWMechanics
int i=0;
for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it)
{
effects.add (*it, it->mMagnMin + (it->mMagnMax - it->mMagnMin) * iter->second[i]);
float random = 1.f;
if (iter->second.find(i) != iter->second.end())
random = iter->second.at(i);
effects.add (*it, it->mMagnMin + (it->mMagnMax - it->mMagnMin) * random);
++i;
}
}
@ -192,9 +206,60 @@ namespace MWMechanics
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin();
effectIt != list.mList.end(); ++effectIt, ++i)
{
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i];
visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, "", magnitude);
float random = 1.f;
if (it->second.find(i) != it->second.end())
random = it->second.at(i);
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random;
visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, -1, magnitude);
}
}
}
bool Spells::canUsePower(const std::string &power) const
{
std::map<std::string, MWWorld::TimeStamp>::const_iterator it = mUsedPowers.find(power);
if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp())
return true;
else
return false;
}
void Spells::usePower(const std::string &power)
{
mUsedPowers[power] = MWBase::Environment::get().getWorld()->getTimeStamp();
}
void Spells::readState(const ESM::SpellState &state)
{
mSpells = state.mSpells;
mSelectedSpell = state.mSelectedSpell;
// Discard spells that are no longer available due to changed content files
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(iter->first);
if (!spell)
{
if (iter->first == mSelectedSpell)
mSelectedSpell = "";
mSpells.erase(iter++);
}
else
++iter;
}
// No need to discard spells here (doesn't really matter if non existent ids are kept)
for (std::map<std::string, ESM::TimeStamp>::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it)
mUsedPowers[it->first] = MWWorld::TimeStamp(it->second);
}
void Spells::writeState(ESM::SpellState &state) const
{
state.mSpells = mSpells;
state.mSelectedSpell = mSelectedSpell;
for (std::map<std::string, MWWorld::TimeStamp>::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it)
state.mUsedPowers[it->first] = it->second.toEsm();
}
}

@ -7,12 +7,16 @@
#include <components/misc/stringops.hpp>
#include "../mwworld/ptr.hpp"
#include "../mwworld/timestamp.hpp"
#include "magiceffects.hpp"
namespace ESM
{
struct Spell;
struct SpellState;
}
namespace MWMechanics
@ -22,21 +26,29 @@ namespace MWMechanics
/// \brief Spell list
///
/// This class manages known spells as well as abilities, powers and permanent negative effects like
/// diseases.
/// diseases. It also keeps track of used powers (which can only be used every 24h).
class Spells
{
public:
typedef std::map<std::string, std::vector<float> > TContainer; // ID, normalised magnitudes
typedef std::map<std::string, std::map<const int, float> > TContainer; // ID, <effect index, normalised random magnitude>
typedef TContainer::const_iterator TIterator;
private:
TContainer mSpells;
// Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different)
std::string mSelectedSpell;
std::map<std::string, MWWorld::TimeStamp> mUsedPowers;
public:
bool canUsePower (const std::string& power) const;
void usePower (const std::string& power);
void purgeCommonDisease();
void purgeBlightDisease();
void purgeCorprusDisease();
@ -72,6 +84,9 @@ namespace MWMechanics
bool hasBlightDisease() const;
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const;
void readState (const ESM::SpellState& state);
void writeState (ESM::SpellState& state) const;
};
}

@ -168,7 +168,10 @@ void LocalMap::requestMap(MWWorld::CellStore* cell, float zMin, float zMax)
mCameraPosNode->setPosition(Vector3(0,0,0));
render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name);
// Note: using force=true for exterior cell maps.
// They must be updated even if they were visited before, because the set of surrounding active cells might be different
// (and objects in a different cell can "bleed" into another cell's map if they cross the border)
render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name, true);
if (mBuffers.find(name) == mBuffers.end())
{
@ -296,7 +299,8 @@ void LocalMap::requestMap(MWWorld::CellStore* cell,
ESM::FogState* fog = cell->getFog();
// We are using the same bounds and angle as we were using when the textures were originally made. Segments should come out the same.
assert (i < int(fog->mFogTextures.size()));
if (i >= int(fog->mFogTextures.size()))
throw std::runtime_error("fog texture count mismatch");
ESM::FogTexture& esm = fog->mFogTextures[i];
loadFogOfWar(texturePrefix, esm);
@ -338,8 +342,6 @@ Ogre::TexturePtr LocalMap::createFogOfWarTexture(const std::string &texName)
PF_A8R8G8B8,
TU_DYNAMIC_WRITE_ONLY);
}
else
tex->unload();
return tex;
}
@ -351,12 +353,14 @@ void LocalMap::loadFogOfWar (const std::string& texturePrefix, ESM::FogTexture&
Ogre::Image image;
image.load(stream, "tga");
assert (image.getWidth() == sFogOfWarResolution && image.getHeight() == sFogOfWarResolution);
if (image.getWidth() != sFogOfWarResolution || image.getHeight() != sFogOfWarResolution)
throw std::runtime_error("fog texture size mismatch");
std::string texName = texturePrefix + "_fog";
Ogre::TexturePtr tex = createFogOfWarTexture(texName);
tex->unload();
tex->loadImage(image);
// create a buffer to use for dynamic operations
@ -369,7 +373,7 @@ void LocalMap::loadFogOfWar (const std::string& texturePrefix, ESM::FogTexture&
void LocalMap::render(const float x, const float y,
const float zlow, const float zhigh,
const float xw, const float yw, const std::string& texture)
const float xw, const float yw, const std::string& texture, bool force)
{
mCellCamera->setFarClipDistance( (zhigh-zlow) + 2000 );
mCellCamera->setNearClipDistance(50);
@ -408,6 +412,11 @@ void LocalMap::render(const float x, const float y,
PF_R8G8B8);
tex->getBuffer()->blit(mRenderTexture->getBuffer());
}
else if (force)
{
mRenderTarget->update();
tex->getBuffer()->blit(mRenderTexture->getBuffer());
}
mRenderingManager->enableLights(true);
mLight->setVisible(false);

@ -106,10 +106,11 @@ namespace MWRender
float mAngle;
const Ogre::Vector2 rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle);
/// @param force Always render, even if we already have a cached map
void render(const float x, const float y,
const float zlow, const float zhigh,
const float xw, const float yw,
const std::string& texture);
const std::string& texture, bool force=false);
// Creates a fog of war texture and initializes it to full black
void createFogOfWar(const std::string& texturePrefix);

@ -35,7 +35,7 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod
mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0);
}
catch (Ogre::Exception& e)
catch (Ogre::Exception&)
{
mSupported = false;
}

@ -380,6 +380,10 @@ void RenderingManager::update (float duration, bool paused)
mCamera->update(duration, paused);
Ogre::SceneNode *node = data.getBaseNode();
Ogre::Quaternion orient = node->_getDerivedOrientation();
mLocalMap->updatePlayer(playerPos, orient);
if(paused)
return;
@ -393,10 +397,6 @@ void RenderingManager::update (float duration, bool paused)
mSkyManager->setGlare(mOcclusionQuery->getSunVisibility());
Ogre::SceneNode *node = data.getBaseNode();
Ogre::Quaternion orient = node->_getDerivedOrientation();
mLocalMap->updatePlayer(playerPos, orient);
mWater->updateUnderwater(world->isUnderwater(player.getCell(), cam));

@ -33,6 +33,7 @@ namespace MWRender
{
public:
WeaponAnimation() : mPitchFactor(0) {}
virtual ~WeaponAnimation() {}
virtual void attachArrow(MWWorld::Ptr actor);
virtual void releaseArrow(MWWorld::Ptr actor);

@ -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; i<arg0; ++i) runtime.pop();
MWMechanics::AiEscort escortPackage(actorID, duration, x, y, z);
MWMechanics::AiEscort escortPackage(actor, duration, x, y, z);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr);
std::cout << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration
@ -126,6 +127,7 @@ namespace MWScript
std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPtr(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; i<arg0; ++i) runtime.pop();
MWMechanics::AiEscort escortPackage(actorID, cellID, duration, x, y, z);
MWMechanics::AiEscort escortPackage(actor, cellID, duration, x, y, z);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr);
std::cout << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration
@ -281,6 +283,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();
@ -297,7 +300,7 @@ namespace MWScript
// discard additional arguments (reset), because we have no idea what they mean.
for (unsigned int i=0; i<arg0; ++i) runtime.pop();
MWMechanics::AiFollow followPackage(actorID, duration, x, y ,z);
MWMechanics::AiFollow followPackage(actor, duration, x, y ,z);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(followPackage, ptr);
std::cout << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration
@ -316,6 +319,7 @@ namespace MWScript
std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPtr(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; i<arg0; ++i) runtime.pop();
MWMechanics::AiFollow followPackage(actorID, cellID, duration, x, y ,z);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(followPackage, ptr);
MWMechanics::AiFollow followPackage(actor, cellID, duration, x, y ,z);
ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(followPackage, ptr);
std::cout << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration
<< std::endl;
}

@ -295,7 +295,7 @@ namespace MWScript
{
store = MWBase::Environment::get().getWorld()->getInterior(cellID);
}
catch(std::exception &e)
catch(std::exception&)
{
const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID);
if(cell)
@ -395,7 +395,7 @@ namespace MWScript
{
store = MWBase::Environment::get().getWorld()->getInterior(cellID);
}
catch(std::exception &e)
catch(std::exception&)
{
const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID);
if(cell)

@ -204,7 +204,7 @@ void FFmpeg_Decoder::open(const std::string &fname)
mFrame = avcodec_alloc_frame();
}
catch(std::exception &e)
catch(std::exception&)
{
if (mFormatCtx->pb->buffer != NULL)
{

@ -288,7 +288,7 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode
mOutput.mActiveSounds.push_back(this);
}
catch(std::exception &e)
catch(std::exception&)
{
alDeleteBuffers(sNumBuffers, mBuffers);
alGetError();
@ -471,7 +471,7 @@ bool OpenAL_SoundStream::process()
mIsFinished = finished;
}
catch(std::exception &e) {
catch(std::exception&) {
std::cout<< "Error updating stream \""<<mDecoder->getName()<<"\"" <<std::endl;
mSamplesQueued = 0;
mIsFinished = true;
@ -781,7 +781,7 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname)
{
decoder->open(fname);
}
catch(Ogre::FileNotFoundException &e)
catch(Ogre::FileNotFoundException&)
{
std::string::size_type pos = fname.rfind('.');
if(pos == std::string::npos)
@ -859,7 +859,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, f
buf = getBuffer(fname);
sound.reset(new OpenAL_Sound(*this, src, buf, Ogre::Vector3(0.0f), vol, basevol, pitch, 1.0f, 1000.0f, flags));
}
catch(std::exception &e)
catch(std::exception&)
{
mFreeSources.push_back(src);
if(buf && alIsBuffer(buf))
@ -898,7 +898,7 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre
buf = getBuffer(fname);
sound.reset(new OpenAL_Sound3D(*this, src, buf, pos, vol, basevol, pitch, min, max, flags));
}
catch(std::exception &e)
catch(std::exception&)
{
mFreeSources.push_back(src);
if(buf && alIsBuffer(buf))
@ -940,7 +940,7 @@ MWBase::SoundPtr OpenAL_Output::streamSound(DecoderPtr decoder, float volume, fl
{
sound.reset(new OpenAL_SoundStream(*this, src, decoder, volume, pitch, flags));
}
catch(std::exception &e)
catch(std::exception&)
{
mFreeSources.push_back(src);
throw;

@ -321,7 +321,7 @@ namespace MWSound
sound = mOutput->playSound(file, volume, basevol, pitch, mode|type, offset);
mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId);
}
catch(std::exception &e)
catch(std::exception&)
{
//std::cout <<"Sound Error: "<<e.what()<< std::endl;
}
@ -349,7 +349,7 @@ namespace MWSound
else
mActiveSounds[sound] = std::make_pair(ptr, soundId);
}
catch(std::exception &e)
catch(std::exception&)
{
//std::cout <<"Sound Error: "<<e.what()<< std::endl;
}

@ -28,6 +28,7 @@
#include "../mwworld/inventorystore.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwscript/globalscripts.hpp"
@ -46,6 +47,8 @@ void MWState::StateManager::cleanup (bool force)
mState = State_NoGame;
mCharacterManager.clearCurrentCharacter();
mTimePlayed = 0;
MWMechanics::CreatureStats::cleanup();
}
}
@ -315,6 +318,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl
case ESM::REC_CSTA:
case ESM::REC_WTHR:
case ESM::REC_DYNA:
case ESM::REC_ACTC:
MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap);
break;
@ -326,6 +330,8 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl
case ESM::REC_GMAP:
case ESM::REC_KEYS:
case ESM::REC_ASPL:
MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val);
break;

@ -3,6 +3,9 @@
#include "class.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
namespace MWWorld
{
ActionApply::ActionApply (const Ptr& target, const std::string& id)
@ -11,6 +14,8 @@ namespace MWWorld
void ActionApply::executeImp (const Ptr& actor)
{
MWBase::Environment::get().getWorld()->breakInvisibility(actor);
MWWorld::Class::get (getTarget()).apply (getTarget(), mId, actor);
}
@ -22,6 +27,8 @@ namespace MWWorld
void ActionApplyWithSkill::executeImp (const Ptr& actor)
{
MWBase::Environment::get().getWorld()->breakInvisibility(actor);
if (MWWorld::Class::get (getTarget()).apply (getTarget(), mId, actor) && mUsageType!=-1)
MWWorld::Class::get (getTarget()).skillUsageSucceeded (actor, mSkillIndex, mUsageType);
}

@ -22,7 +22,6 @@ namespace MWWorld
std::list<MWWorld::Ptr> followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor);
for(std::list<MWWorld::Ptr>::iterator it = followers.begin();it != followers.end();++it)
{
std::cout << "teleporting someone!" << (*it).getCellRef().mRefID;
executeImp(*it);
}

@ -12,10 +12,14 @@
#include <components/esm/npcstate.hpp>
#include <components/esm/creaturestate.hpp>
#include <components/esm/fogstate.hpp>
#include <components/esm/creaturelevliststate.hpp>
#include <components/esm/doorstate.hpp>
#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<typename T>
MWWorld::Ptr searchViaActorId (MWWorld::CellRefList<T>& actorList, int actorId,
MWWorld::CellStore *cell)
{
for (typename MWWorld::CellRefList<T>::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<typename RecordType, typename T>
void writeReferenceCollection (ESM::ESMWriter& writer,
const MWWorld::CellRefList<T>& 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<ESM::ObjectState> (writer, mClothes);
writeReferenceCollection<ESM::ContainerState> (writer, mContainers);
writeReferenceCollection<ESM::CreatureState> (writer, mCreatures);
writeReferenceCollection<ESM::ObjectState> (writer, mDoors);
writeReferenceCollection<ESM::DoorState> (writer, mDoors);
writeReferenceCollection<ESM::ObjectState> (writer, mIngreds);
writeReferenceCollection<ESM::ObjectState> (writer, mCreatureLists);
writeReferenceCollection<ESM::CreatureLevListState> (writer, mCreatureLists);
writeReferenceCollection<ESM::ObjectState> (writer, mItemLists);
writeReferenceCollection<ESM::LightState> (writer, mLights);
writeReferenceCollection<ESM::ObjectState> (writer, mLockpicks);
@ -629,7 +660,7 @@ namespace MWWorld
case ESM::REC_DOOR:
readReferenceCollection<ESM::ObjectState> (reader, mDoors, contentFileMap);
readReferenceCollection<ESM::DoorState> (reader, mDoors, contentFileMap);
break;
case ESM::REC_INGR:
@ -639,7 +670,7 @@ namespace MWWorld
case ESM::REC_LEVC:
readReferenceCollection<ESM::ObjectState> (reader, mCreatureLists, contentFileMap);
readReferenceCollection<ESM::CreatureLevListState> (reader, mCreatureLists, contentFileMap);
break;
case ESM::REC_LEVI:

@ -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);

@ -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");
}
}

@ -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;
};
}

@ -199,7 +199,7 @@ void ESMStore::setUp()
if (!mRaces.find (player->mRace) ||
!mClasses.find (player->mClass))
throw std::runtime_error ("Invalid player record (race or class unavilable");
throw std::runtime_error ("Invalid player record (race or class unavailable");
}
return true;

@ -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;
}

@ -87,6 +87,7 @@ namespace MWWorld
float mMultiplier;
};
// TODO: store in savegame
typedef std::map<std::string, std::vector<EffectParams> > TEffectMagnitudes;
TEffectMagnitudes mPermanentMagicEffectMagnitudes;

@ -635,12 +635,12 @@ namespace MWWorld
bool cmode = act->getCollisionMode();
if(cmode)
{
act->enableCollisions(false);
act->enableCollisionMode(false);
return false;
}
else
{
act->enableCollisions(true);
act->enableCollisionMode(true);
return true;
}
}

@ -85,7 +85,17 @@ namespace
namespace MWWorld
{
void Scene::update (float duration, bool paused){
void Scene::update (float duration, bool paused)
{
if (mNeedMapUpdate)
{
// Note: exterior cell maps must be updated, even if they were visited before, because the set of surrounding cells might be different
// (and objects in a different cell can "bleed" into another cells map if they cross the border)
for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active)
mRendering.requestMap(*active);
mNeedMapUpdate = false;
}
mRendering.update (duration, paused);
}
@ -197,8 +207,9 @@ namespace MWWorld
mRendering.updateTerrain();
for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active)
mRendering.requestMap(*active);
// Delay the map update until scripts have been given a chance to run.
// If we don't do this, objects that should be disabled will still appear on the map.
mNeedMapUpdate = true;
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
}
@ -342,7 +353,7 @@ namespace MWWorld
//We need the ogre renderer and a scene node.
Scene::Scene (MWRender::RenderingManager& rendering, PhysicsSystem *physics)
: mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering)
: mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNeedMapUpdate(false)
{
}
@ -496,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();
}
}

@ -58,6 +58,8 @@ namespace MWWorld
PhysicsSystem *mPhysics;
MWRender::RenderingManager& mRendering;
bool mNeedMapUpdate;
void playerCellChange (CellStore *cell, const ESM::Position& position,
bool adjustPlayerPos = true);
@ -102,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);
};
}

@ -1,10 +1,10 @@
#include "timestamp.hpp"
#include <cmath>
#include <stdexcept>
#include <components/esm/defs.hpp>
namespace MWWorld
{
TimeStamp::TimeStamp (float hour, int day)
@ -105,4 +105,18 @@ namespace MWWorld
return hours + 24*days;
}
ESM::TimeStamp TimeStamp::toEsm() const
{
ESM::TimeStamp ret;
ret.mDay = mDay;
ret.mHour = mHour;
return ret;
}
TimeStamp::TimeStamp(const ESM::TimeStamp &esm)
{
mDay = esm.mDay;
mHour = esm.mHour;
}
}

@ -1,6 +1,11 @@
#ifndef GAME_MWWORLD_TIMESTAMP_H
#define GAME_MWWORLD_TIMESTAMP_H
namespace ESM
{
class TimeStamp;
}
namespace MWWorld
{
/// \brief In-game time stamp
@ -14,9 +19,12 @@ namespace MWWorld
public:
explicit TimeStamp (float hour = 0, int day = 0);
///< \oaram hour [0, 23)
///< \param hour [0, 23)
/// \param day >=0
explicit TimeStamp (const ESM::TimeStamp& esm);
ESM::TimeStamp toEsm () const;
float getHour() const;
int getDay() const;

@ -30,6 +30,7 @@
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/levelledlist.hpp"
#include "../mwmechanics/combat.hpp"
#include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors
#include "../mwrender/sky.hpp"
#include "../mwrender/animation.hpp"
@ -274,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
@ -287,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);
@ -297,6 +302,12 @@ namespace MWWorld
void World::readRecord (ESM::ESMReader& reader, int32_t type,
const std::map<int, int>& contentFileMap)
{
if (type == ESM::REC_ACTC)
{
MWMechanics::CreatureStats::readActorIdCounter(reader);
return;
}
if (!mStore.readRecord (reader, type) &&
!mGlobalVariables.readRecord (reader, type) &&
!mPlayer->readRecord (reader, type) &&
@ -546,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)
@ -1195,29 +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<std::string> collisions = mPhysics->getCollisions(it->first);
for (std::vector<std::string>::iterator cit = collisions.begin(); cit != collisions.end(); ++cit)
{
MWWorld::Ptr ptr = getPtrViaHandle(*cit);
if (MWWorld::Class::get(ptr).isActor())
if (ptr.getClass().isActor())
{
// we collided with an actor, we need to undo the rotation
// Collided with actor, ask actor to try to avoid door
if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr() ) {
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);
break;
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;
}
@ -1491,6 +1521,9 @@ namespace MWWorld
{
MWWorld::LiveCellRef<ESM::Door>& ref = *it;
if (!ref.mData.isEnabled())
continue;
if (ref.mRef.mTeleport)
{
World::DoorMarker newMarker;
@ -1832,25 +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;
int state = open ? 1 : 2;
door.getClass().setDoorState(door, state);
mDoorStates[door] = state;
}
bool World::getPlayerStandingOn (const MWWorld::Ptr& object)
@ -1919,7 +1959,7 @@ namespace MWWorld
out.push_back(searchPtrViaHandle(*it));
}
}
bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc)
{
if (!targetNpc.getRefData().isEnabled() || !npc.getRefData().isEnabled())
@ -1954,7 +1994,7 @@ namespace MWWorld
{
OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle());
physicActor->enableCollisions(enable);
physicActor->enableCollisionBody(enable);
}
bool World::findInteriorPosition(const std::string &name, ESM::Position &pos)
@ -2161,8 +2201,8 @@ namespace MWWorld
// If this is a power, check if it was already used in the last 24h
if (!fail && spell->mData.mType == ESM::Spell::ST_Power)
{
if (stats.canUsePower(spell->mId))
stats.usePower(spell->mId);
if (stats.getSpells().canUsePower(spell->mId))
stats.getSpells().usePower(spell->mId);
else
{
message = "#{sPowerAlreadyUsed}";
@ -2214,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;
@ -2301,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;
@ -2365,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)
@ -2451,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;
@ -2474,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);

@ -88,7 +88,7 @@ namespace MWWorld
float mFacedDistance;
std::map<MWWorld::Ptr, int> 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,10 +496,11 @@ namespace MWWorld
virtual void setupPlayer();
virtual void renderPlayer();
virtual bool getOpenOrCloseDoor(const MWWorld::Ptr& door);
///< if activated, should this door be opened or closed?
/// open or close a non-teleport door (depending on current state)
virtual void activateDoor(const MWWorld::Ptr& door);
///< activate (open or close) an non-teleport 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

@ -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
npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate
)
add_component_dir (misc

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save