merge
openmw-30
graffy76 11 years ago
commit 35d1502308

@ -434,7 +434,6 @@ IF(NOT WIN32 AND NOT APPLE)
# Install licenses
INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" )
INSTALL(FILES "OFL.txt" DESTINATION "${LICDIR}" )
INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" )
ENDIF (DPKG_PROGRAM)

@ -333,7 +333,7 @@ int load(Arguments& info)
// Is the user interested in this record type?
bool interested = true;
if (info.types.size() > 0)
if (!info.types.empty())
{
std::vector<std::string>::iterator match;
match = std::find(info.types.begin(), info.types.end(),

@ -124,7 +124,7 @@ void printEffectList(ESM::EffectList effects)
{
int i = 0;
std::vector<ESM::ENAMstruct>::iterator eit;
for (eit = effects.mList.begin(); eit != effects.mList.end(); eit++)
for (eit = effects.mList.begin(); eit != effects.mList.end(); ++eit)
{
std::cout << " Effect[" << i << "]: " << magicEffectLabel(eit->mEffectID)
<< " (" << eit->mEffectID << ")" << std::endl;

@ -214,13 +214,13 @@ QStringList Launcher::GraphicsPage::getAvailableOptions(const QString &key, Ogre
uint row = 0;
Ogre::ConfigOptionMap options = renderer->getConfigOptions();
for (Ogre::ConfigOptionMap::iterator i = options.begin (); i != options.end (); i++, row++)
for (Ogre::ConfigOptionMap::iterator i = options.begin (); i != options.end (); ++i, ++row)
{
Ogre::StringVector::iterator opt_it;
uint idx = 0;
for (opt_it = i->second.possibleValues.begin();
opt_it != i->second.possibleValues.end(); opt_it++, idx++)
opt_it != i->second.possibleValues.end(); ++opt_it, ++idx)
{
if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) {
result << ((key == "FSAA") ? QString("MSAA ") : QString("")) + QString::fromStdString((*opt_it).c_str()).simplified();

@ -121,7 +121,7 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
//iterate the data directories and add them to the file dialog for loading
for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter)
{
QString path = QString::fromStdString(iter->string());
QString path = QString::fromUtf8 (iter->string().c_str());
mFileDialog.addFiles(path);
}
/*

@ -251,13 +251,13 @@ CSMSettings::UserSettings::~UserSettings()
void CSMSettings::UserSettings::loadSettings (const QString &fileName)
{
mUserFilePath = QString::fromUtf8
(mCfgMgr.getUserConfigPath().c_str()) + fileName.toUtf8();
(mCfgMgr.getUserConfigPath().string().c_str()) + fileName.toUtf8();
QString global = QString::fromUtf8
(mCfgMgr.getGlobalPath().c_str()) + fileName.toUtf8();
(mCfgMgr.getGlobalPath().string().c_str()) + fileName.toUtf8();
QString local = QString::fromUtf8
(mCfgMgr.getLocalPath().c_str()) + fileName.toUtf8();
(mCfgMgr.getLocalPath().string().c_str()) + fileName.toUtf8();
//open user and global streams
QTextStream *userStream = openFilestream (mUserFilePath, true);

@ -25,7 +25,7 @@ CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values,
void CSVWorld::DataDisplayDelegate::buildPixmaps ()
{
if (mPixmaps.size() > 0)
if (!mPixmaps.empty())
mPixmaps.clear();
IconList::iterator it = mIcons.begin();
@ -33,7 +33,7 @@ void CSVWorld::DataDisplayDelegate::buildPixmaps ()
while (it != mIcons.end())
{
mPixmaps.push_back (std::make_pair (it->first, it->second.pixmap (mIconSize) ) );
it++;
++it;
}
}

@ -183,7 +183,7 @@ CSVWorld::CommandDelegate* CSVWorld::DialogueDelegateDispatcher::makeDelegate(CS
{
delegate = CommandDelegateFactoryCollection::get().makeDelegate (
display, mUndoStack, mParent);
mDelegates.insert(std::make_pair<int, CommandDelegate*>(display, delegate));
mDelegates.insert(std::make_pair(display, delegate));
} else
{
delegate = delegateIt->second;

@ -298,7 +298,7 @@ void CSVWorld::Table::revertRecord()
{
std::vector<std::string> revertableIds = listRevertableSelectedIds();
if (revertableIds.size()>0)
if (!revertableIds.empty())
{
if (revertableIds.size()>1)
mDocument.getUndoStack().beginMacro (tr ("Revert multiple records"));
@ -318,7 +318,7 @@ void CSVWorld::Table::deleteRecord()
{
std::vector<std::string> deletableIds = listDeletableSelectedIds();
if (deletableIds.size()>0)
if (!deletableIds.empty())
{
if (deletableIds.size()>1)
mDocument.getUndoStack().beginMacro (tr ("Delete multiple records"));

@ -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 aipersue alchemy aiwander aitravel aifollow
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow
aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
disease pickpocket levelledlist combat steering obstacle
)

@ -5,6 +5,11 @@
#include <stdint.h>
namespace Loading
{
class Listener;
}
namespace ESM
{
class ESMReader;
@ -60,7 +65,7 @@ namespace MWBase
virtual int countSavedGameRecords() const = 0;
virtual void write (ESM::ESMWriter& writer) const = 0;
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const = 0;
virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0;
};

@ -11,6 +11,11 @@
#include "../mwdialogue/topic.hpp"
#include "../mwdialogue/quest.hpp"
namespace Loading
{
class Listener;
}
namespace ESM
{
class ESMReader;
@ -80,7 +85,7 @@ namespace MWBase
virtual int countSavedGameRecords() const = 0;
virtual void write (ESM::ESMWriter& writer) const = 0;
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const = 0;
virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0;
};

@ -55,6 +55,8 @@ namespace MWBase
virtual void endGame() = 0;
virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot) = 0;
virtual void saveGame (const std::string& description, const MWState::Slot *slot = 0) = 0;
///< Write a saved game to \a slot or create a new slot if \a slot == 0.
///

@ -156,8 +156,9 @@ namespace MWBase
virtual void setValue (const std::string& id, int value) = 0;
/// Set time left for the player to start drowning (update the drowning bar)
/// @param time value from [0,20]
virtual void setDrowningTimeLeft (float time) =0;
/// @param time time left to start drowning
/// @param maxTime how long we can be underwater (in total) until drowning starts
virtual void setDrowningTimeLeft (float time, float maxTime) = 0;
virtual void setPlayerClass (const ESM::Class &class_) = 0;
///< set current class of player
@ -302,8 +303,12 @@ namespace MWBase
/// Clear all savegame-specific data
virtual void clear() = 0;
virtual void write (ESM::ESMWriter& writer) = 0;
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) = 0;
virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0;
virtual int countSavedGameRecords() const = 0;
/// Does the current stack of GUI-windows permit saving?
virtual bool isSavingAllowed() const = 0;
};
}

@ -108,7 +108,7 @@ namespace MWBase
virtual int countSavedGameRecords() const = 0;
virtual void write (ESM::ESMWriter& writer) const = 0;
virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const = 0;
virtual void readRecord (ESM::ESMReader& reader, int32_t type,
const std::map<int, int>& contentFileMap) = 0;
@ -407,6 +407,8 @@ namespace MWBase
virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0;
///< get Line of Sight (morrowind stupid implementation)
virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist) = 0;
virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0;
virtual int canRest() = 0;

@ -624,6 +624,8 @@ namespace MWClass
if (!attacker.isEmpty() && ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() <= 30)
MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault);
getCreatureStats(ptr).setAttacked(true);
if(!successful)
{
// TODO: Handle HitAttemptOnMe script function
@ -659,7 +661,6 @@ namespace MWClass
{
MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
}
getCreatureStats(ptr).setAttacked(true);
// Check for knockdown
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat();

@ -170,6 +170,14 @@ namespace MWClass
virtual int getBaseGold(const MWWorld::Ptr& ptr) const;
virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const;
virtual bool canSwim (const MWWorld::Ptr &ptr) const {
return true;
}
virtual bool canWalk (const MWWorld::Ptr &ptr) const {
return true;
}
};
}

@ -609,7 +609,7 @@ namespace MWDialogue
return 1; // known topics
}
void DialogueManager::write (ESM::ESMWriter& writer) const
void DialogueManager::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
{
ESM::DialogueState state;
@ -621,6 +621,7 @@ namespace MWDialogue
writer.startRecord (ESM::REC_DIAS);
state.save (writer);
writer.endRecord (ESM::REC_DIAS);
progress.increaseProgress();
}
void DialogueManager::readRecord (ESM::ESMReader& reader, int32_t type)

@ -83,7 +83,7 @@ namespace MWDialogue
virtual int countSavedGameRecords() const;
virtual void write (ESM::ESMWriter& writer) const;
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const;
virtual void readRecord (ESM::ESMReader& reader, int32_t type);
};

@ -167,7 +167,7 @@ namespace MWDialogue
return count;
}
void Journal::write (ESM::ESMWriter& writer) const
void Journal::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
{
for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter)
{
@ -178,6 +178,7 @@ namespace MWDialogue
writer.startRecord (ESM::REC_QUES);
state.save (writer);
writer.endRecord (ESM::REC_QUES);
progress.increaseProgress();
for (Topic::TEntryIter iter (quest.begin()); iter!=quest.end(); ++iter)
{
@ -188,6 +189,7 @@ namespace MWDialogue
writer.startRecord (ESM::REC_JOUR);
entry.save (writer);
writer.endRecord (ESM::REC_JOUR);
progress.increaseProgress();
}
}
@ -199,6 +201,7 @@ namespace MWDialogue
writer.startRecord (ESM::REC_JOUR);
entry.save (writer);
writer.endRecord (ESM::REC_JOUR);
progress.increaseProgress();
}
for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter)
@ -214,6 +217,7 @@ namespace MWDialogue
writer.startRecord (ESM::REC_JOUR);
entry.save (writer);
writer.endRecord (ESM::REC_JOUR);
progress.increaseProgress();
}
}
}

@ -64,7 +64,7 @@ namespace MWDialogue
virtual int countSavedGameRecords() const;
virtual void write (ESM::ESMWriter& writer) const;
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const;
virtual void readRecord (ESM::ESMReader& reader, int32_t type);
};

@ -188,12 +188,13 @@ namespace MWGui
break;
case GM_ClassCreate:
MWBase::Environment::get().getWindowManager()->removeDialog(mCreateClassDialog);
mCreateClassDialog = 0;
mCreateClassDialog = new CreateClassDialog();
if (!mCreateClassDialog)
{
mCreateClassDialog = new CreateClassDialog();
mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone);
mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack);
}
mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen);
mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone);
mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack);
mCreateClassDialog->setVisible(true);
if (mCreationStage < CSE_RaceChosen)
mCreationStage = CSE_RaceChosen;
@ -531,8 +532,8 @@ namespace MWGui
mPlayerClass = klass;
MWBase::Environment::get().getWindowManager()->setPlayerClass(klass);
MWBase::Environment::get().getWindowManager()->removeDialog(mCreateClassDialog);
mCreateClassDialog = 0;
// Do not delete dialog, so that choices are rembered in case we want to go back and adjust them later
mCreateClassDialog->setVisible(false);
}
updatePlayerHealth();
@ -554,8 +555,8 @@ namespace MWGui
void CharacterCreation::onCreateClassDialogBack()
{
MWBase::Environment::get().getWindowManager()->removeDialog(mCreateClassDialog);
mCreateClassDialog = 0;
// Do not delete dialog, so that choices are rembered in case we want to go back and adjust them later
mCreateClassDialog->setVisible(false);
MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class);

@ -10,7 +10,7 @@ namespace MWGui
{
}
void CompanionItemModel::copyItem (const ItemStack& item, size_t count)
void CompanionItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner=false)
{
if (mActor.getClass().isNpc())
{
@ -18,7 +18,7 @@ namespace MWGui
stats.modifyProfit(MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);
}
InventoryItemModel::copyItem(item, count);
InventoryItemModel::copyItem(item, count, setNewOwner);
}
void CompanionItemModel::removeItem (const ItemStack& item, size_t count)

@ -13,7 +13,7 @@ namespace MWGui
public:
CompanionItemModel (const MWWorld::Ptr& actor);
virtual void copyItem (const ItemStack& item, size_t count);
virtual void copyItem (const ItemStack& item, size_t count, bool setNewOwner);
virtual void removeItem (const ItemStack& item, size_t count);
};

@ -215,16 +215,22 @@ namespace MWGui
{
std::vector<std::string> matches;
listNames();
mCommandLine->setCaption(complete( mCommandLine->getOnlyText(), matches ));
#if 0
int i = 0;
for(std::vector<std::string>::iterator it=matches.begin(); it < matches.end(); ++it,++i )
std::string oldCaption = mCommandLine->getCaption();
std::string newCaption = complete( mCommandLine->getOnlyText(), matches );
mCommandLine->setCaption(newCaption);
// List candidates if repeatedly pressing tab
if (oldCaption == newCaption && matches.size())
{
printOK( *it );
if( i == 50 )
break;
int i = 0;
printOK("");
for(std::vector<std::string>::iterator it=matches.begin(); it < matches.end(); ++it,++i )
{
printOK( *it );
if( i == 50 )
break;
}
}
#endif
}
if(mCommandHistory.empty()) return;

@ -13,6 +13,7 @@
#include "../mwworld/containerstore.hpp"
#include "../mwmechanics/pickpocket.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "countdialog.hpp"
#include "tradewindow.hpp"
@ -84,8 +85,7 @@ namespace MWGui
// otherwise, do the transfer
if (targetModel != mSourceModel)
{
targetModel->copyItem(mItem, mDraggedCount);
mSourceModel->removeItem(mItem, mDraggedCount);
mSourceModel->moveItem(mItem, mDraggedCount, targetModel);
}
mSourceModel->update();
@ -292,8 +292,7 @@ namespace MWGui
if (!onTakeItem(item, item.mCount))
break;
playerModel->copyItem(item, item.mCount);
mModel->removeItem(item, item.mCount);
mModel->moveItem(item, item.mCount, playerModel);
}
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container);
@ -341,7 +340,11 @@ namespace MWGui
}
else
{
MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item.mBase, count);
// Looting a dead corpse is considered OK
if (mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead())
return true;
else
MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item.mBase, count);
}
return true;
}

@ -71,7 +71,7 @@ ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item)
return -1;
}
void ContainerItemModel::copyItem (const ItemStack& item, size_t count)
void 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))

@ -21,7 +21,7 @@ namespace MWGui
virtual ModelIndex getIndex (ItemStack item);
virtual size_t getItemCount();
virtual void copyItem (const ItemStack& item, size_t count);
virtual void copyItem (const ItemStack& item, size_t count, bool setNewOwner=false);
virtual void removeItem (const ItemStack& item, size_t count);
virtual void update();

@ -196,6 +196,16 @@ namespace MWGui
bitmapFile->read(&textureData[0], width*height*4);
bitmapFile->close();
std::string resourceName;
if (name.size() >= 5 && Misc::StringUtils::ciEqual(name.substr(0, 5), "magic"))
resourceName = "Magic Cards";
else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "century"))
resourceName = "Century Gothic";
else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "daedric"))
resourceName = "Daedric";
else
return; // no point in loading it, since there is no way of using additional fonts
std::string textureName = name;
Ogre::Image image;
image.loadDynamicImage(&textureData[0], width, height, Ogre::PF_BYTE_RGBA);
@ -208,18 +218,11 @@ namespace MWGui
// Register the font with MyGUI
MyGUI::ResourceManualFont* font = static_cast<MyGUI::ResourceManualFont*>(
MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont"));
// We need to emulate loading from XML because the data members are private as of mygui 3.2.0
MyGUI::xml::Document xmlDocument;
MyGUI::xml::ElementPtr root = xmlDocument.createRoot("ResourceManualFont");
if (name.size() >= 5 && Misc::StringUtils::ciEqual(name.substr(0, 5), "magic"))
root->addAttribute("name", "Magic Cards");
else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "century"))
root->addAttribute("name", "Century Gothic");
else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "daedric"))
root->addAttribute("name", "Daedric");
else
return; // no point in loading it, since there is no way of using additional fonts
root->addAttribute("name", resourceName);
MyGUI::xml::ElementPtr defaultHeight = root->createChild("Property");
defaultHeight->addAttribute("key", "DefaultHeight");
@ -285,6 +288,7 @@ namespace MWGui
font->deserialization(root, MyGUI::Version(3,2,0));
MyGUI::ResourceManager::getInstance().removeByName(font->getResourceName());
MyGUI::ResourceManager::getInstance().addResource(font);
}

@ -203,9 +203,9 @@ namespace MWGui
}
}
void HUD::setDrowningTimeLeft(float time)
void HUD::setDrowningTimeLeft(float time, float maxTime)
{
size_t progress = time/20.0*200.0;
size_t progress = time/maxTime*200.0;
mDrowning->setProgressPosition(progress);
bool isDrowning = (progress == 0);
@ -625,7 +625,7 @@ namespace MWGui
if (mIsDrowning)
{
float intensity = (cos(mDrowningFlashTheta) + 1.0f) / 2.0f;
mDrowningFlash->setColour(MyGUI::Colour(intensity, intensity, intensity));
mDrowningFlash->setColour(MyGUI::Colour(intensity, 0, 0));
}
}

@ -22,8 +22,9 @@ namespace MWGui
void setBatchCount(unsigned int count);
/// Set time left for the player to start drowning
/// @param time value from [0,20]
void setDrowningTimeLeft(float time);
/// @param time time left to start drowning
/// @param maxTime how long we can be underwater (in total) until drowning starts
void setDrowningTimeLeft(float time, float maxTime);
void setDrowningBarVisible(bool visible);
void setHmsVisible(bool visible);

@ -4,6 +4,8 @@
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwmechanics/creaturestats.hpp"
namespace MWGui
{
@ -38,11 +40,11 @@ ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item)
return -1;
}
void InventoryItemModel::copyItem (const ItemStack& item, size_t count)
void 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);
mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor, setNewOwner);
}
@ -57,6 +59,18 @@ 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)
{
bool setNewOwner = false;
// Are you dead? Then you wont need that anymore
if (mActor.getClass().isActor() && mActor.getClass().getCreatureStats(mActor).isDead())
setNewOwner = true;
otherModel->copyItem(item, count, setNewOwner);
removeItem(item, count);
}
void InventoryItemModel::update()
{
MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(mActor);

@ -15,9 +15,12 @@ namespace MWGui
virtual ModelIndex getIndex (ItemStack item);
virtual size_t getItemCount();
virtual void copyItem (const ItemStack& item, size_t count);
virtual void 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 void update();
protected:

@ -35,7 +35,7 @@ namespace MWGui
, mTrading(false)
, mLastXSize(0)
, mLastYSize(0)
, mPreview(MWBase::Environment::get().getWorld ()->getPlayerPtr())
, mPreview(new MWRender::InventoryPreview(MWBase::Environment::get().getWorld ()->getPlayerPtr()))
, mPreviewDirty(true)
, mDragAndDrop(dragAndDrop)
, mSelectedItem(-1)
@ -91,8 +91,8 @@ namespace MWGui
mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr());
mSortModel = new SortFilterItemModel(mTradeModel);
mItemView->setModel(mSortModel);
mPreview = MWRender::InventoryPreview(mPtr);
mPreview.setup();
mPreview.reset(new MWRender::InventoryPreview(mPtr));
mPreview->setup();
}
void InventoryWindow::setGuiMode(GuiMode mode)
@ -444,7 +444,7 @@ namespace MWGui
MWWorld::Ptr InventoryWindow::getAvatarSelectedItem(int x, int y)
{
int slot = mPreview.getSlotSelected (x, y);
int slot = mPreview->getSlotSelected (x, y);
if (slot == -1)
return MWWorld::Ptr();
@ -493,7 +493,7 @@ namespace MWGui
mPreviewDirty = false;
MyGUI::IntSize size = mAvatarImage->getSize();
mPreview.update (size.width, size.height);
mPreview->update (size.width, size.height);
mAvatarImage->setImageTexture("CharacterPreview");
mAvatarImage->setImageCoord(MyGUI::IntCoord(0, 0, std::min(512, size.width), std::min(1024, size.height)));

@ -34,7 +34,7 @@ namespace MWGui
MWWorld::Ptr getAvatarSelectedItem(int x, int y);
void rebuildAvatar() {
mPreview.rebuild();
mPreview->rebuild();
}
TradeItemModel* getTradeModel();
@ -81,7 +81,7 @@ namespace MWGui
int mLastXSize;
int mLastYSize;
MWRender::InventoryPreview mPreview;
std::auto_ptr<MWRender::InventoryPreview> mPreview;
bool mTrading;

@ -71,16 +71,22 @@ namespace MWGui
{
}
void ItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel)
{
otherModel->copyItem(item, count);
removeItem(item, count);
}
ProxyItemModel::~ProxyItemModel()
{
delete mSourceModel;
}
void ProxyItemModel::copyItem (const ItemStack& item, size_t count)
void 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);
mSourceModel->copyItem (item, count, setNewOwner);
}
void ProxyItemModel::removeItem (const ItemStack& item, size_t count)

@ -55,7 +55,11 @@ namespace MWGui
virtual void update() = 0;
virtual void copyItem (const ItemStack& item, size_t count) = 0;
/// Move items from this model to \a otherModel.
virtual void 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 void removeItem (const ItemStack& item, size_t count) = 0;
private:
@ -69,7 +73,7 @@ namespace MWGui
{
public:
virtual ~ProxyItemModel();
virtual void copyItem (const ItemStack& item, size_t count);
virtual void copyItem (const ItemStack& item, size_t count, bool setNewOwner=false);
virtual void removeItem (const ItemStack& item, size_t count);
virtual ModelIndex getIndex (ItemStack item);

@ -48,7 +48,7 @@ namespace MWGui
void MWList::redraw(bool scrollbarShown)
{
const int _scrollBarWidth = 24; // fetch this from skin?
const int _scrollBarWidth = 20; // fetch this from skin?
const int scrollBarWidth = scrollbarShown ? _scrollBarWidth : 0;
const int spacing = 3;
size_t scrollbarPosition = mScrollView->getScrollPosition();
@ -83,7 +83,7 @@ namespace MWGui
else
{
MyGUI::ImageBox* separator = mScrollView->createWidget<MyGUI::ImageBox>("MW_HLine",
MyGUI::IntCoord(2, mItemHeight, mScrollView->getWidth()-4, 18),
MyGUI::IntCoord(2, mItemHeight, mScrollView->getWidth() - scrollBarWidth - 4, 18),
MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch);
separator->setNeedMouseFocus(false);
@ -91,7 +91,7 @@ namespace MWGui
}
++i;
}
mScrollView->setCanvasSize(mClient->getSize().width + (_scrollBarWidth-scrollBarWidth), std::max(mItemHeight, mClient->getSize().height));
mScrollView->setCanvasSize(mClient->getSize().width, std::max(mItemHeight, mClient->getSize().height));
if (!scrollbarShown && mItemHeight > mClient->getSize().height)
redraw(true);

@ -160,7 +160,6 @@ namespace MWGui
void LoadingScreen::setProgress (size_t value)
{
assert(value < mProgressBar->getScrollRange());
if (value - mProgress < mProgressBar->getScrollRange()/100.f)
return;
mProgress = value;
@ -174,7 +173,6 @@ namespace MWGui
mProgressBar->setScrollPosition(0);
size_t value = mProgress + increase;
mProgress = value;
assert(mProgress < mProgressBar->getScrollRange());
mProgressBar->setTrackSize(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize());
draw();
}

@ -25,7 +25,7 @@ namespace MWGui
virtual void setProgressRange (size_t range);
virtual void setProgress (size_t value);
virtual void increaseProgress (size_t increase);
virtual void increaseProgress (size_t increase=1);
virtual void setVisible(bool visible);

@ -28,7 +28,7 @@ namespace MWGui
{
getWidget(mVersionText, "VersionText");
std::stringstream sstream;
sstream << "OpenMW version: " << OPENMW_VERSION;
sstream << "OpenMW Version: " << OPENMW_VERSION;
// adding info about git hash if available
std::string rev = OPENMW_VERSION_COMMITHASH;
@ -36,7 +36,7 @@ namespace MWGui
if (!rev.empty() && !tag.empty())
{
rev = rev.substr(0,10);
sstream << "\nrevision: " << rev;
sstream << "\nRevision: " << rev;
}
std::string output = sstream.str();
@ -170,7 +170,8 @@ namespace MWGui
buttons.push_back("loadgame");
if (state==MWBase::StateManager::State_Running &&
MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1)
MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1 &&
MWBase::Environment::get().getWindowManager()->isSavingAllowed())
buttons.push_back("savegame");
buttons.push_back("options");

@ -595,7 +595,7 @@ namespace MWGui
MyGUI::Gui::getInstance().destroyWidget(mGlobalMapOverlay->getChildAt(0));
}
void MapWindow::write(ESM::ESMWriter &writer)
void MapWindow::write(ESM::ESMWriter &writer, Loading::Listener& progress)
{
ESM::GlobalMap map;
mGlobalMapRender->write(map);
@ -605,6 +605,7 @@ namespace MWGui
writer.startRecord(ESM::REC_GMAP);
map.save(writer);
writer.endRecord(ESM::REC_GMAP);
progress.increaseProgress();
}
void MapWindow::readRecord(ESM::ESMReader &reader, int32_t type)

@ -108,7 +108,7 @@ namespace MWGui
/// Clear all savegame-specific data
void clear();
void write (ESM::ESMWriter& writer);
void write (ESM::ESMWriter& writer, Loading::Listener& progress);
void readRecord (ESM::ESMReader& reader, int32_t type);
private:

@ -2,6 +2,8 @@
#include <boost/lexical_cast.hpp>
#include <components/esm/quickkeys.hpp>
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp"
@ -55,6 +57,14 @@ namespace MWGui
}
}
void QuickKeysMenu::clear()
{
for (int i=0; i<10; ++i)
{
unassign(mQuickKeyButtons[i], i);
}
}
QuickKeysMenu::~QuickKeysMenu()
{
delete mAssignDialog;
@ -154,8 +164,6 @@ namespace MWGui
frame->setUserString ("ToolTipType", "ItemPtr");
frame->setUserData(item);
frame->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked);
MyGUI::ImageBox* image = frame->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default);
std::string path = std::string("icons\\");
path += MWWorld::Class::get(item).getInventoryIcon(item);
@ -165,7 +173,8 @@ namespace MWGui
image->setImageTexture (path);
image->setNeedMouseFocus (false);
mItemSelectionDialog->setVisible(false);
if (mItemSelectionDialog)
mItemSelectionDialog->setVisible(false);
}
void QuickKeysMenu::onAssignItemCancel()
@ -198,7 +207,8 @@ namespace MWGui
image->setImageTexture (path);
image->setNeedMouseFocus (false);
mMagicSelectionDialog->setVisible(false);
if (mMagicSelectionDialog)
mMagicSelectionDialog->setVisible(false);
}
void QuickKeysMenu::onAssignMagic (const std::string& spellId)
@ -239,7 +249,8 @@ namespace MWGui
image->setImageTexture (path);
image->setNeedMouseFocus (false);
mMagicSelectionDialog->setVisible(false);
if (mMagicSelectionDialog)
mMagicSelectionDialog->setVisible(false);
}
void QuickKeysMenu::onAssignMagicCancel ()
@ -374,6 +385,110 @@ namespace MWGui
center();
}
void QuickKeysMenu::write(ESM::ESMWriter &writer)
{
writer.startRecord(ESM::REC_KEYS);
ESM::QuickKeys keys;
for (int i=0; i<10; ++i)
{
MyGUI::Button* button = mQuickKeyButtons[i];
int type = *button->getUserData<QuickKeyType>();
ESM::QuickKeys::QuickKey key;
key.mType = type;
switch (type)
{
case Type_Unassigned:
break;
case Type_Item:
case Type_MagicItem:
{
MWWorld::Ptr item = *button->getChildAt(0)->getUserData<MWWorld::Ptr>();
key.mId = item.getCellRef().mRefID;
break;
}
case Type_Magic:
std::string spellId = button->getChildAt(0)->getUserString("Spell");
key.mId = spellId;
break;
}
keys.mKeys.push_back(key);
}
keys.save(writer);
writer.endRecord(ESM::REC_KEYS);
}
void QuickKeysMenu::readRecord(ESM::ESMReader &reader, int32_t type)
{
if (type != ESM::REC_KEYS)
return;
ESM::QuickKeys keys;
keys.load(reader);
int i=0;
for (std::vector<ESM::QuickKeys::QuickKey>::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it)
{
if (i >= 10)
return;
mSelectedIndex = i;
int keyType = it->mType;
std::string id = it->mId;
MyGUI::Button* button = mQuickKeyButtons[i];
switch (keyType)
{
case Type_Magic:
onAssignMagic(id);
break;
case Type_Item:
case Type_MagicItem:
{
// Find the item by id
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
MWWorld::Ptr item;
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{
if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, id))
{
if (item.isEmpty() ||
// Prefer the stack with the lowest remaining uses
(it->getCellRef().mCharge != -1 && (item.getCellRef().mCharge == -1 || it->getCellRef().mCharge < item.getCellRef().mCharge) ))
{
item = *it;
}
}
}
if (item.isEmpty())
unassign(button, i);
else
{
if (keyType == Type_Item)
onAssignItem(item);
else if (keyType == Type_MagicItem)
onAssignMagicItem(item);
}
break;
}
case Type_Unassigned:
unassign(button, i);
break;
}
++i;
}
}
// ---------------------------------------------------------------------------------------------------------

@ -41,6 +41,11 @@ namespace MWGui
};
void write (ESM::ESMWriter& writer);
void readRecord (ESM::ESMReader& reader, int32_t type);
void clear();
private:
MyGUI::EditBox* mInstructionLabel;
MyGUI::Button* mOkButton;

@ -17,6 +17,8 @@ namespace MWGui
void checkReferenceAvailable(); ///< closes the window, if the MW-reference has become unavailable
void resetReference() { mPtr = MWWorld::Ptr(); mCurrentPlayerCell = NULL; }
protected:
virtual void onReferenceUnavailable() = 0; ///< called when reference has become unavailable

@ -23,6 +23,7 @@ namespace MWGui
: WindowModal("openmw_savegame_dialog.layout")
, mSaving(true)
, mCurrentCharacter(NULL)
, mCurrentSlot(NULL)
{
getWidget(mScreenshot, "Screenshot");
getWidget(mCharacterSelection, "SelectCharacter");
@ -36,6 +37,7 @@ namespace MWGui
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked);
mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected);
mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected);
mSaveList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SaveGameDialog::onSlotMouseClick);
mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated);
mSaveNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onEditSelectAccept);
mSaveNameEdit->eventEditTextChange += MyGUI::newDelegate(this, &SaveGameDialog::onSaveNameChanged);
@ -47,6 +49,37 @@ namespace MWGui
accept();
}
void SaveGameDialog::onSlotMouseClick(MyGUI::ListBox* sender, size_t pos)
{
onSlotSelected(sender, pos);
if (pos != MyGUI::ITEM_NONE && MyGUI::InputManager::getInstance().isShiftPressed())
{
ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog();
dialog->open("#{sMessage3}");
dialog->eventOkClicked.clear();
dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed);
dialog->eventCancelClicked.clear();
}
}
void SaveGameDialog::onDeleteSlotConfirmed()
{
MWBase::Environment::get().getStateManager()->deleteGame (mCurrentCharacter, mCurrentSlot);
mSaveList->removeItemAt(mSaveList->getIndexSelected());
onSlotSelected(mSaveList, MyGUI::ITEM_NONE);
// The character might be deleted now
size_t previousIndex = mCharacterSelection->getIndexSelected();
open();
if (mCharacterSelection->getItemCount())
{
size_t nextCharacter = std::min(previousIndex, mCharacterSelection->getItemCount()-1);
mCharacterSelection->setIndexSelected(nextCharacter);
onCharacterSelected(mCharacterSelection, nextCharacter);
}
}
void SaveGameDialog::onSaveNameChanged(MyGUI::EditBox *sender)
{
// This might have previously been a save slot from the list. If so, that is no longer the case
@ -69,6 +102,12 @@ namespace MWGui
center();
mCharacterSelection->setCaption("");
mCharacterSelection->removeAllItems();
mCurrentCharacter = NULL;
mCurrentSlot = NULL;
mSaveList->removeAllItems();
MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager();
if (mgr->characterBegin() == mgr->characterEnd())
return;
@ -78,8 +117,6 @@ namespace MWGui
std::string directory =
Misc::StringUtils::lowerCase (Settings::Manager::getString ("character", "Saves"));
mCharacterSelection->removeAllItems();
int selectedIndex = MyGUI::ITEM_NONE;
for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it)
@ -152,23 +189,10 @@ namespace MWGui
{
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL);
// Get the selected slot, if any
unsigned int i=0;
const MWState::Slot* slot = NULL;
if (mCurrentCharacter)
{
for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it,++i)
{
if (i == mSaveList->getIndexSelected())
slot = &*it;
}
}
if (mSaving)
{
// If overwriting an existing slot, ask for confirmation first
if (slot != NULL && !reallySure)
if (mCurrentSlot != NULL && !reallySure)
{
ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog();
dialog->open("#{sMessage4}");
@ -182,19 +206,23 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage65}");
return;
}
MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), slot);
}
setVisible(false);
MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu);
if (mSaving)
{
MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), mCurrentSlot);
}
else
{
if (mCurrentCharacter && slot)
if (mCurrentCharacter && mCurrentSlot)
{
MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot);
MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu);
MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot);
}
}
setVisible(false);
if (MWBase::Environment::get().getStateManager()->getState()==
MWBase::StateManager::State_NoGame)
{
@ -221,6 +249,7 @@ namespace MWGui
assert(character && "Can't find selected character");
mCurrentCharacter = character;
mCurrentSlot = NULL;
fillSaveList();
}
@ -240,6 +269,7 @@ namespace MWGui
{
if (pos == MyGUI::ITEM_NONE)
{
mCurrentSlot = NULL;
mInfoText->setCaption("");
mScreenshot->setImageTexture("");
return;
@ -248,17 +278,17 @@ namespace MWGui
if (mSaving)
mSaveNameEdit->setCaption(sender->getItemNameAt(pos));
const MWState::Slot* slot = NULL;
mCurrentSlot = NULL;
unsigned int i=0;
for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i)
{
if (i == pos)
slot = &*it;
mCurrentSlot = &*it;
}
assert(slot && "Can't find selected slot");
assert(mCurrentSlot && "Can't find selected slot");
std::stringstream text;
time_t time = slot->mTimeStamp;
time_t time = mCurrentSlot->mTimeStamp;
struct tm* timeinfo;
timeinfo = localtime(&time);
@ -269,24 +299,24 @@ namespace MWGui
char buffer[size];
if (std::strftime(buffer, size, "%x %X", timeinfo) > 0)
text << buffer << "\n";
text << "Level " << slot->mProfile.mPlayerLevel << "\n";
text << slot->mProfile.mPlayerCell << "\n";
text << "Level " << mCurrentSlot->mProfile.mPlayerLevel << "\n";
text << mCurrentSlot->mProfile.mPlayerCell << "\n";
// text << "Time played: " << slot->mProfile.mTimePlayed << "\n";
int hour = int(slot->mProfile.mInGameTime.mGameHour);
int hour = int(mCurrentSlot->mProfile.mInGameTime.mGameHour);
bool pm = hour >= 12;
if (hour >= 13) hour -= 12;
if (hour == 0) hour = 12;
text
<< slot->mProfile.mInGameTime.mDay << " "
<< MWBase::Environment::get().getWorld()->getMonthName(slot->mProfile.mInGameTime.mMonth)
<< mCurrentSlot->mProfile.mInGameTime.mDay << " "
<< MWBase::Environment::get().getWorld()->getMonthName(mCurrentSlot->mProfile.mInGameTime.mMonth)
<< " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}");
mInfoText->setCaptionWithReplacing(text.str());
// Decode screenshot
std::vector<char> data = slot->mProfile.mScreenshot; // MemoryDataStream doesn't work with const data :(
std::vector<char> data = mCurrentSlot->mProfile.mScreenshot; // MemoryDataStream doesn't work with const data :(
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
Ogre::Image image;
image.load(stream, "jpg");

@ -6,6 +6,7 @@
namespace MWState
{
class Character;
class Slot;
}
namespace MWGui
@ -24,8 +25,15 @@ namespace MWGui
void onCancelButtonClicked (MyGUI::Widget* sender);
void onOkButtonClicked (MyGUI::Widget* sender);
void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos);
// Slot selected (mouse click or arrow keys)
void onSlotSelected (MyGUI::ListBox* sender, size_t pos);
// Slot activated (double click or enter key)
void onSlotActivated (MyGUI::ListBox* sender, size_t pos);
// Slot clicked with mouse
void onSlotMouseClick(MyGUI::ListBox* sender, size_t pos);
void onDeleteSlotConfirmed();
void onEditSelectAccept (MyGUI::EditBox* sender);
void onSaveNameChanged (MyGUI::EditBox* sender);
void onConfirmationGiven();
@ -46,6 +54,7 @@ namespace MWGui
MyGUI::Widget* mSpacer;
const MWState::Character* mCurrentCharacter;
const MWState::Slot* mCurrentSlot;
};

@ -475,7 +475,7 @@ namespace MWGui
text += std::string("#DDC79E") + faction->mName;
if (expelled.find(it->first) != expelled.end())
text += "\n#{sExpelled}";
text += "\n#BF9959#{sExpelled}";
else
{
text += std::string("\n#BF9959") + faction->mRanks[it->second];

@ -120,14 +120,11 @@ namespace MWGui
if (i == sourceModel->getItemCount())
throw std::runtime_error("The borrowed item disappeared");
// reset owner before copying
// reset owner while copying, but only for items bought by the player
bool setNewOwner = (mMerchant.isEmpty());
const ItemStack& item = sourceModel->getItem(i);
std::string owner = item.mBase.getCellRef().mOwner;
if (mMerchant.isEmpty()) // only for items bought by player
item.mBase.getCellRef().mOwner = "";
// copy the borrowed items to our model
copyItem(item, it->mCount);
item.mBase.getCellRef().mOwner = owner;
copyItem(item, it->mCount, setNewOwner);
// then remove them from the source model
sourceModel->removeItem(item, it->mCount);
}

@ -78,13 +78,13 @@ namespace MWGui
}
void TradeWindow::startTrade(const MWWorld::Ptr& actor)
{
{
mPtr = actor;
mCurrentBalance = 0;
mCurrentMerchantOffer = 0;
checkTradeTime();
checkTradeTime();
std::vector<MWWorld::Ptr> itemSources;
MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources);
@ -245,7 +245,7 @@ namespace MWGui
// were there any items traded at all?
std::vector<ItemStack> playerBought = playerItemModel->getItemsBorrowedToUs();
std::vector<ItemStack> merchantBought = mTradeModel->getItemsBorrowedToUs();
if (!playerBought.size() && !merchantBought.size())
if (playerBought.empty() && merchantBought.empty())
{
// user notification
MWBase::Environment::get().getWindowManager()->
@ -476,7 +476,7 @@ namespace MWGui
}
// Relates to NPC gold reset delay
void TradeWindow::checkTradeTime()
void TradeWindow::checkTradeTime()
{
MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr);
double delay = boost::lexical_cast<double>(MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fBarterGoldResetDelay")->getInt());
@ -488,14 +488,14 @@ namespace MWGui
}
}
void TradeWindow::updateTradeTime()
void TradeWindow::updateTradeTime()
{
MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr);
MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr);
double delay = boost::lexical_cast<double>(MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fBarterGoldResetDelay")->getInt());
// If trade timestamp is within reset delay don't set
if ( ! (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getTradeTime() &&
if ( ! (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getTradeTime() &&
MWBase::Environment::get().getWorld()->getTimeStamp() < sellerStats.getTradeTime() + delay) )
{
sellerStats.setTradeTime(MWBase::Environment::get().getWorld()->getTimeStamp());

@ -625,9 +625,9 @@ namespace MWGui
mStatsWindow->setValue (id, value);
}
void WindowManager::setDrowningTimeLeft (float time)
void WindowManager::setDrowningTimeLeft (float time, float maxTime)
{
mHud->setDrowningTimeLeft(time);
mHud->setDrowningTimeLeft(time, maxTime);
}
void WindowManager::setPlayerClass (const ESM::Class &class_)
@ -1405,16 +1405,49 @@ namespace MWGui
void WindowManager::clear()
{
mMap->clear();
mQuickKeysMenu->clear();
mTrainingWindow->resetReference();
mDialogueWindow->resetReference();
mTradeWindow->resetReference();
mSpellBuyingWindow->resetReference();
mSpellCreationDialog->resetReference();
mEnchantingDialog->resetReference();
mContainerWindow->resetReference();
mCompanionWindow->resetReference();
mConsole->resetReference();
mGuiModes.clear();
updateVisible();
}
void WindowManager::write(ESM::ESMWriter &writer)
void WindowManager::write(ESM::ESMWriter &writer, Loading::Listener& progress)
{
mMap->write(writer);
mMap->write(writer, progress);
mQuickKeysMenu->write(writer);
progress.increaseProgress();
}
void WindowManager::readRecord(ESM::ESMReader &reader, int32_t type)
{
mMap->readRecord(reader, type);
if (type == ESM::REC_GMAP)
mMap->readRecord(reader, type);
else if (type == ESM::REC_KEYS)
mQuickKeysMenu->readRecord(reader, type);
}
int WindowManager::countSavedGameRecords() const
{
return 1 // Global map
+ 1; // QuickKeysMenu
}
bool WindowManager::isSavingAllowed() const
{
return !MyGUI::InputManager::getInstance().isModalAny()
// TODO: remove this, once we have properly serialized the state of open windows
&& (!isGuiMode() || (mGuiModes.size() == 1 && getMode() == GM_MainMenu));
}
void WindowManager::playVideo(const std::string &name, bool allowSkipping)

@ -166,8 +166,9 @@ namespace MWGui
virtual void setValue (const std::string& id, int value);
/// Set time left for the player to start drowning (update the drowning bar)
/// @param time value from [0,20]
virtual void setDrowningTimeLeft (float time);
/// @param time time left to start drowning
/// @param maxTime how long we can be underwater (in total) until drowning starts
virtual void setDrowningTimeLeft (float time, float maxTime);
virtual void setPlayerClass (const ESM::Class &class_); ///< set current class of player
virtual void configureSkills (const SkillList& major, const SkillList& minor); ///< configure skill groups, each set contains the skill ID for that group.
@ -290,8 +291,12 @@ namespace MWGui
/// Clear all savegame-specific data
virtual void clear();
virtual void write (ESM::ESMWriter& writer);
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress);
virtual void readRecord (ESM::ESMReader& reader, int32_t type);
virtual int countSavedGameRecords() const;
/// Does the current stack of GUI-windows permit saving?
virtual bool isSavingAllowed() const;
private:
bool mConsoleOnlyScripts;

@ -206,7 +206,7 @@ namespace MWMechanics
if (effectIt->mKey.mId == effectId)
effectIt = it->second.mEffects.erase(effectIt);
else
effectIt++;
++effectIt;
}
}
mSpellsChanged = true;
@ -224,7 +224,7 @@ namespace MWMechanics
&& it->second.mCasterHandle == actorHandle)
effectIt = it->second.mEffects.erase(effectIt);
else
effectIt++;
++effectIt;
}
}
mSpellsChanged = true;

@ -29,7 +29,7 @@
#include "aicombat.hpp"
#include "aifollow.hpp"
#include "aipersue.hpp"
#include "aipursue.hpp"
namespace
{
@ -194,21 +194,11 @@ namespace MWMechanics
+(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1])
+(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2]));
float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified();
float disp = 100; //creatures don't have disposition, so set it to 100 by default
if(ptr.getTypeName() == typeid(ESM::NPC).name())
{
disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr);
}
if( (fight == 100 )
|| (fight >= 95 && d <= 3000)
|| (fight >= 90 && d <= 2000)
|| (fight >= 80 && d <= 1000)
|| (fight >= 80 && disp <= 40)
|| (fight >= 70 && disp <= 35 && d <= 1000)
|| (fight >= 60 && disp <= 30 && d <= 1000)
|| (fight >= 50 && disp == 0)
|| (fight >= 40 && disp <= 10 && d <= 500)
)
{
bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player)
@ -216,7 +206,7 @@ namespace MWMechanics
if (LOS)
{
creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr()));
creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr()), ptr);
creatureStats.setHostile(true);
}
}
@ -547,7 +537,7 @@ namespace MWMechanics
// 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);
MWWorld::Class::get (ref.getPtr()).getCreatureStats (ref.getPtr()).getAiSequence().stack(package, ptr);
// TODO: VFX_SummonStart, VFX_SummonEnd
creatureStats.mSummonedCreatures.insert(std::make_pair(it->first,
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos).getRefData().getHandle()));
@ -729,34 +719,33 @@ namespace MWMechanics
CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr);
NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats(ptr);
// If I'm a guard and I'm not hostile
if (ptr.getClass().isClass(ptr, "Guard") && !creatureStats.isHostile())
if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.isHostile())
{
/// \todo Move me! I shouldn't be here...
const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
float cutoff = float(esmStore.get<ESM::GameSetting>().find("iCrimeThreshold")->getInt()) *
float(esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->getInt()) *
esmStore.get<ESM::GameSetting>().find("fCrimeGoldDiscountMult")->getFloat();
// Attack on sight if bounty is greater than the cutoff
float cutoff = float(esmStore.get<ESM::GameSetting>().find("iCrimeThreshold")->getInt());
// Force dialogue on sight if bounty is greater than the cutoff
// In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty)
if ( player.getClass().getNpcStats(player).getBounty() >= cutoff
// TODO: do not run these two every frame. keep an Aware state for each actor and update it every 0.2 s or so?
&& MWBase::Environment::get().getWorld()->getLOS(ptr, player)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr))
{
creatureStats.getAiSequence().stack(AiCombat(player));
creatureStats.setHostile(true);
npcStats.setCrimeId( MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() );
creatureStats.getAiSequence().stack(AiPursue(player.getClass().getId(player)), ptr);
creatureStats.setAlarmed(true);
npcStats.setCrimeId(MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId());
}
}
// if I was a witness to a crime
if (npcStats.getCrimeId() != -1)
{
// if you've payed for your crimes and I havent noticed
// if you've paid for your crimes and I havent noticed
if( npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() )
{
// Calm witness down
if (ptr.getClass().isClass(ptr, "Guard"))
creatureStats.getAiSequence().stopPersue();
creatureStats.getAiSequence().stopPursuit();
creatureStats.getAiSequence().stopCombat();
// Reset factors to attack
@ -771,17 +760,16 @@ namespace MWMechanics
else if (!creatureStats.isHostile())
{
if (ptr.getClass().isClass(ptr, "Guard"))
creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player)));
creatureStats.getAiSequence().stack(AiPursue(player.getClass().getId(player)), ptr);
else
creatureStats.getAiSequence().stack(AiCombat(player));
creatureStats.getAiSequence().stack(AiCombat(player), ptr);
creatureStats.setHostile(true);
}
}
// if I didn't report a crime was I attacked?
else if (creatureStats.getAttacked() && !creatureStats.isHostile())
{
creatureStats.getAiSequence().stack(AiCombat(player));
creatureStats.getAiSequence().stack(AiCombat(player), ptr);
creatureStats.setHostile(true);
}
}
@ -889,12 +877,23 @@ namespace MWMechanics
iter->second->update(duration);
}
// Kill dead actors
// Kill dead actors, update some variables
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++)
{
const MWWorld::Class &cls = MWWorld::Class::get(iter->first);
CreatureStats &stats = cls.getCreatureStats(iter->first);
//KnockedOutOneFrameLogic
//Used for "OnKnockedOut" command
//Put here to ensure that it's run for PRECISELY one frame.
if(stats.getKnockedDown() && !stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) { //Start it for one frame if nessesary
stats.setKnockedDownOneFrame(true);
}
else if (stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) { //Turn off KnockedOutOneframe
stats.setKnockedDownOneFrame(false);
stats.setKnockedDownOverOneFrame(true);
}
if(!stats.isDead())
{
if(iter->second->isDead())
@ -1028,8 +1027,7 @@ namespace MWMechanics
{
const MWWorld::Class &cls = MWWorld::Class::get(iter->first);
CreatureStats &stats = cls.getCreatureStats(iter->first);
if(stats.getAiSequence().getTypeId() == AiPackage::TypeIdFollow)
if(!stats.isDead() && stats.getAiSequence().getTypeId() == AiPackage::TypeIdFollow)
{
MWMechanics::AiFollow* package = static_cast<MWMechanics::AiFollow*>(stats.getAiSequence().getActivePackage());
if(package->getFollowedActor() == actor.getCellRef().mRefID)
@ -1050,8 +1048,7 @@ namespace MWMechanics
{
const MWWorld::Class &cls = MWWorld::Class::get(*iter);
CreatureStats &stats = cls.getCreatureStats(*iter);
if(stats.getAiSequence().getTypeId() == AiPackage::TypeIdCombat)
if(!stats.isDead() && stats.getAiSequence().getTypeId() == AiPackage::TypeIdCombat)
{
MWMechanics::AiCombat* package = static_cast<MWMechanics::AiCombat*>(stats.getAiSequence().getActivePackage());
if(package->getTargetId() == actor.getCellRef().mRefID)

@ -31,10 +31,52 @@ namespace
//chooses an attack depending on probability to avoid uniformity
void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement);
float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f)
{
float len = (dirLen > 0.0f)? dirLen : dir.length();
return Ogre::Radian( Ogre::Math::ACos(dir.y / len) * sgn(Ogre::Math::ASin(dir.x / len)) ).valueDegrees();
}
float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f)
{
float len = (dirLen > 0.0f)? dirLen : dir.length();
return Ogre::Radian(-Ogre::Math::ASin(dir.z / len)).valueDegrees();
}
const float PATHFIND_Z_REACH = 50.0f;
// distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid
const float PATHFIND_CAUTION_DIST = 500.0f;
// distance after which actor (failed previously to shortcut) will try again
const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f;
// cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target;
// magnitude of pits/obstacles is defined by PATHFIND_Z_REACH
bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offset)
{
if((to - from).length() >= PATHFIND_CAUTION_DIST || abs(from.z - to.z) <= PATHFIND_Z_REACH)
{
Ogre::Vector3 dir = to - from;
dir.z = 0;
dir.normalise();
float verticalOffset = 200; // instead of '200' here we want the height of the actor
Ogre::Vector3 _from = from + dir*offset + Ogre::Vector3::UNIT_Z * verticalOffset;
// cast up-down ray and find height in world space of hit
float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1);
if(abs(from.z - h) <= PATHFIND_Z_REACH)
return true;
}
return false;
}
}
namespace MWMechanics
{
static const float MAX_ATTACK_DURATION = 0.35f;
static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander
// NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp
@ -45,16 +87,16 @@ namespace MWMechanics
mTimerCombatMove(0),
mFollowTarget(false),
mReadyToAttack(false),
mStrike(false),
mAttack(false),
mCombatMove(false),
mBackOffDoor(false),
mRotate(false),
mMovement(),
mForceNoShortcut(false),
mShortcutFailPos(),
mBackOffDoor(false),
mCell(NULL),
mDoorIter(actor.getCell()->get<ESM::Door>().mList.end()),
mDoors(actor.getCell()->get<ESM::Door>()),
mDoorCheckDuration(0),
mTargetAngle(0)
mDoorCheckDuration(0)
{
}
@ -125,17 +167,23 @@ namespace MWMechanics
mCombatMove = false;
}
}
actor.getClass().getMovementSettings(actor) = mMovement;
actor.getClass().getMovementSettings(actor).mRotation[0] = 0;
actor.getClass().getMovementSettings(actor).mRotation[2] = 0;
if (mRotate)
if(mMovement.mRotation[2] != 0)
{
if(zTurn(actor, Ogre::Degree(mMovement.mRotation[2]))) mMovement.mRotation[2] = 0;
}
if(mMovement.mRotation[0] != 0)
{
if (zTurn(actor, Ogre::Degree(mTargetAngle)))
mRotate = false;
if(smoothTurn(actor, Ogre::Degree(mMovement.mRotation[0]), 0)) mMovement.mRotation[0] = 0;
}
mTimerAttack -= duration;
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike);
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack);
float tReaction = 0.25f;
if(mTimerReact < tReaction)
@ -145,7 +193,7 @@ namespace MWMechanics
}
//Update with period = tReaction
mTimerReact = 0;
bool cellChange = mCell && (actor.getCell() != mCell);
@ -156,16 +204,16 @@ namespace MWMechanics
//actual attacking logic
//TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f
float attackPeriod = 1.0f;
float attacksPeriod = 1.0f;
if(mReadyToAttack)
{
if(mTimerAttack <= -attackPeriod)
if(mTimerAttack <= -attacksPeriod)
{
//TODO: should depend on time between 'start' to 'min attack'
//for better controlling of NPCs' attack strength.
//Also it seems that this time is different for slash/thrust/chop
mTimerAttack = 0.35f * static_cast<float>(rand())/RAND_MAX;
mStrike = true;
mTimerAttack = MAX_ATTACK_DURATION * static_cast<float>(rand())/RAND_MAX;
mAttack = true;
//say a provoking combat phrase
if (actor.getClass().isNpc())
@ -180,30 +228,31 @@ namespace MWMechanics
}
}
else if (mTimerAttack <= 0)
mStrike = false;
mAttack = false;
}
else
{
mTimerAttack = -attackPeriod;
mStrike = false;
mTimerAttack = -attacksPeriod;
mAttack = false;
}
const MWWorld::Class &cls = actor.getClass();
const MWWorld::Class &actorCls = actor.getClass();
const ESM::Weapon *weapon = NULL;
MWMechanics::WeaponType weaptype;
float weapRange, weapSpeed = 1.0f;
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
actorCls.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
if (actor.getClass().hasInventoryStore(actor))
// Get weapon characteristics
if (actorCls.hasInventoryStore(actor))
{
MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState();
MWMechanics::DrawState_ state = actorCls.getCreatureStats(actor).getDrawState();
if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
actor.getClass().getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon);
actorCls.getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon);
//Get weapon speed and range
MWWorld::ContainerStoreIterator weaponSlot =
MWMechanics::getActiveWeapon(cls.getCreatureStats(actor), cls.getInventoryStore(actor), &weaptype);
MWMechanics::getActiveWeapon(actorCls.getCreatureStats(actor), actorCls.getInventoryStore(actor), &weaptype);
if (weaptype == WeapType_HandToHand)
{
@ -225,36 +274,27 @@ namespace MWMechanics
weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit)
}
ESM::Position pos = actor.getRefData().getPosition();
/*
* Some notes on meanings of variables:
*
* rangeMelee:
* rangeAttack:
*
* - Distance where attack using the actor's weapon is possible
* - longer for ranged weapons (obviously?) vs. melee weapons
* - Distance where attack using the actor's weapon is possible:
* longer for ranged weapons (obviously?) vs. melee weapons
* - Determined by weapon's reach parameter; hardcoded value
* for ranged weapon and for creatures
* - Once within this distance mFollowTarget is triggered
* (TODO: check whether the follow logic still works for ranged
* weapons, since rangeCloseup is set to zero)
* - TODO: The variable name is confusing. It was ok when AiCombat only
* had melee weapons but now that ranged weapons are supported that is
* no longer the case. It should really be renamed to something
* like rangeStrike - alternatively, keep this name for melee
* weapons and use a different variable for tracking ranged weapon
* distance (rangeRanged maybe?)
*
* rangeCloseup:
* rangeFollow:
*
* - Applies to melee weapons or hand to hand only (or creatures without
* weapons)
* - Distance a little further away from the actor's weapon strike
* i.e. rangeCloseup > rangeMelee for melee weapons
* (the variable names make this simple concept counter-intuitive,
* something like rangeMelee > rangeStrike may be better)
* - Distance a little further away than the actor's weapon reach
* i.e. rangeFollow > rangeAttack for melee weapons
* - Hardcoded value (0 for ranged weapons)
* - Once the target gets beyond this distance mFollowTarget is cleared
* and a path to the target needs to be found
* - TODO: Possibly rename this variable to rangeMelee or even rangeFollow
*
* mFollowTarget:
*
@ -263,58 +303,72 @@ namespace MWMechanics
* available, since the default path without pathgrids is direct to
* target even if LOS is not achieved)
*/
float rangeMelee;
float rangeCloseUp;
float rangeAttack;
float rangeFollow;
bool distantCombat = false;
if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_Thrown)
if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown)
{
rangeMelee = 1000; // TODO: should depend on archer skill
rangeCloseUp = 0; //doesn't needed when attacking from distance
rangeAttack = 1000; // TODO: should depend on archer skill
rangeFollow = 0; // not needed in ranged combat
distantCombat = true;
}
else
{
rangeMelee = weapRange;
rangeCloseUp = 300;
rangeAttack = weapRange;
rangeFollow = 300;
}
ESM::Position pos = actor.getRefData().getPosition();
Ogre::Vector3 vActorPos(pos.pos);
Ogre::Vector3 vTargetPos(mTarget.getRefData().getPosition().pos);
Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos;
bool isStuck = false;
float speed = 0.0f;
if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = actorCls.getSpeed(actor)) / 10.0f)
isStuck = true;
mLastPos = pos;
// check if actor can move along z-axis
bool canMoveByZ = (actorCls.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|| MWBase::Environment::get().getWorld()->isFlying(actor);
Ogre::Vector3 vStart(pos.pos[0], pos.pos[1], pos.pos[2]);
ESM::Position targetPos = mTarget.getRefData().getPosition();
Ogre::Vector3 vDest(targetPos.pos[0], targetPos.pos[1], targetPos.pos[2]);
Ogre::Vector3 vDir = vDest - vStart;
float distBetween = vDir.length();
// determine vertical angle to target
// if actor can move along z-axis it will control movement dir
// if can't - it will control correct aiming
mMovement.mRotation[0] = getXAngleToDir(vDirToTarget);
vDirToTarget.z = 0;
float distToTarget = vDirToTarget.length();
// (within strike dist) || (not quite strike dist while following)
if(distBetween < rangeMelee || (distBetween <= rangeCloseUp && mFollowTarget) )
if(distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck) )
{
//Melee and Close-up combat
vDir.z = 0;
float dirLen = vDir.length();
mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees();
mRotate = true;
mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget);
//bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget);
// (not quite strike dist while following)
if (mFollowTarget && distBetween > rangeMelee)
if (mFollowTarget && distToTarget > rangeAttack)
{
//Close-up combat: just run up on target
mMovement.mPosition[1] = 1;
}
else // (within strike dist)
{
//Melee: stop running and attack
mMovement.mPosition[1] = 0;
// When attacking with a weapon, choose between slash, thrust or chop
if (actor.getClass().hasInventoryStore(actor))
chooseBestAttack(weapon, mMovement);
// set slash/thrust/chop attack
if (mAttack && !distantCombat) chooseBestAttack(weapon, mMovement);
if(mMovement.mPosition[0] || mMovement.mPosition[1])
{
mTimerCombatMove = 0.1f + 0.1f * static_cast<float>(rand())/RAND_MAX;
mCombatMove = true;
}
else if(actor.getClass().isNpc() && (!distantCombat || (distantCombat && rangeMelee/5)))
// only NPCs are smart enough to use dodge movements
else if(actorCls.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2)))
{
//apply sideway movement (kind of dodging) with some probability
if(static_cast<float>(rand())/RAND_MAX < 0.25)
@ -325,7 +379,7 @@ namespace MWMechanics
}
}
if(distantCombat && distBetween < rangeMelee/4)
if(distantCombat && distToTarget < rangeAttack/4)
{
mMovement.mPosition[1] = -1;
}
@ -335,56 +389,105 @@ namespace MWMechanics
mFollowTarget = true;
}
}
else
else // remote pathfinding
{
//target is at far distance: build path to target
mFollowTarget = false;
buildNewPath(actor); //may fail to build a path, check before use
bool preferShortcut = false;
bool inLOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget);
if(mReadyToAttack) isStuck = false;
// check if shortcut is available
if(!isStuck
&& (!mForceNoShortcut
|| (Ogre::Vector3(mShortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)
&& inLOS)
{
if(speed == 0.0f) speed = actorCls.getSpeed(actor);
// maximum dist before pit/obstacle for actor to avoid them depending on his speed
float maxAvoidDist = tReaction * speed + speed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability
preferShortcut = checkWayIsClear(vActorPos, vTargetPos, distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2);
}
//delete visited path node
mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]);
// don't use pathgrid when actor can move in 3 dimensions
if(canMoveByZ) preferShortcut = true;
//if no new path leave mTargetAngle unchanged
if(!mPathFinder.getPath().empty())
if(preferShortcut)
{
//try shortcut
if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget))
mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees();
else
mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
mRotate = true;
mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget);
mForceNoShortcut = false;
mShortcutFailPos.pos[0] = mShortcutFailPos.pos[1] = mShortcutFailPos.pos[2] = 0;
mPathFinder.clearPath();
}
else // if shortcut failed stick to path grid
{
if(!isStuck && mShortcutFailPos.pos[0] == 0.0f && mShortcutFailPos.pos[1] == 0.0f && mShortcutFailPos.pos[2] == 0.0f)
{
mForceNoShortcut = true;
mShortcutFailPos = pos;
}
mFollowTarget = false;
buildNewPath(actor); //may fail to build a path, check before use
//delete visited path node
mPathFinder.checkWaypoint(pos.pos[0],pos.pos[1],pos.pos[2]);
// This works on the borders between the path grid and areas with no waypoints.
if(inLOS && mPathFinder.getPath().size() > 1)
{
// get point just before target
std::list<ESM::Pathgrid::Point>::const_iterator pntIter = --mPathFinder.getPath().end();
--pntIter;
Ogre::Vector3 vBeforeTarget = Ogre::Vector3(pntIter->mX, pntIter->mY, pntIter->mZ);
// if current actor pos is closer to target then last point of path (excluding target itself) then go straight on target
if(distToTarget <= (vTargetPos - vBeforeTarget).length())
{
mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget);
preferShortcut = true;
}
}
// if there is no new path, then go straight on target
if(!preferShortcut)
{
if(!mPathFinder.getPath().empty())
mMovement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
else
mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget);
}
}
mMovement.mPosition[1] = 1;
mReadyToAttack = false;
}
if(distBetween > rangeMelee)
if(distToTarget > rangeAttack && !distantCombat)
{
//special run attack; it shouldn't affect melee combat tactics
if(actor.getClass().getMovementSettings(actor).mPosition[1] == 1)
if(actorCls.getMovementSettings(actor).mPosition[1] == 1)
{
//check if actor can overcome the distance = distToTarget - attackerWeapRange
//less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing)
//then start attacking
float speed1 = cls.getSpeed(actor);
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)
speed2 = 0;
float s1 = distBetween - weapRange;
float s1 = distToTarget - weapRange;
float t = s1/speed1;
float s2 = speed2 * t;
float t_swing = 0.17f/weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags
float t_swing = (MAX_ATTACK_DURATION/2) / weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags
if (t + s2/speed1 <= t_swing)
{
mReadyToAttack = true;
if(mTimerAttack <= -attackPeriod)
if(mTimerAttack <= -attacksPeriod)
{
mTimerAttack = 0.3f*static_cast<float>(rand())/RAND_MAX;
mStrike = true;
mTimerAttack = MAX_ATTACK_DURATION * static_cast<float>(rand())/RAND_MAX;
mAttack = true;
}
}
}
@ -394,7 +497,7 @@ namespace MWMechanics
// coded at 250ms or 1/4 second
//
// TODO: Add a parameter to vary DURATION_SAME_SPOT?
if((distBetween > rangeMelee || mFollowTarget) &&
if((distToTarget > rangeAttack || mFollowTarget) &&
mObstacleCheck.check(actor, tReaction)) // check if evasive action needed
{
// first check if we're walking into a door
@ -406,12 +509,11 @@ namespace MWMechanics
// Check all the doors in this cell
mDoors = cell->get<ESM::Door>(); // update
mDoorIter = mDoors.mList.begin();
Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos);
for (; mDoorIter != mDoors.mList.end(); ++mDoorIter)
{
MWWorld::LiveCellRef<ESM::Door>& ref = *mDoorIter;
float minSqr = 1.3*1.3*MIN_DIST_TO_DOOR_SQUARED; // for legibility
if(actorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr &&
if(vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr &&
ref.mData.getLocalRotation().rot[2] < 0.4f) // even small opening
{
//std::cout<<"closed door id \""<<ref.mRef.mRefID<<"\""<<std::endl;
@ -441,11 +543,10 @@ namespace MWMechanics
}
MWWorld::LiveCellRef<ESM::Door>& ref = *mDoorIter;
Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos);
float minSqr = 1.6 * 1.6 * MIN_DIST_TO_DOOR_SQUARED; // for legibility
// TODO: add reaction to checking open doors
if(mBackOffDoor &&
actorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr)
vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr)
{
mMovement.mPosition[1] = -0.2; // back off, but slowly
}
@ -454,47 +555,38 @@ namespace MWMechanics
ref.mData.getLocalRotation().rot[2] >= 1)
{
mDoorIter = mDoors.mList.end();
mBackOffDoor = false;
mBackOffDoor = false;
//std::cout<<"open door id \""<<ref.mRef.mRefID<<"\""<<std::endl;
mMovement.mPosition[1] = 1;
}
else
{
mMovement.mPosition[1] = 1; // FIXME: oscillation?
}
actor.getClass().getMovementSettings(actor) = mMovement;
// these lines break ranged combat distance keeping
//else
//{
// mMovement.mPosition[1] = 1; // FIXME: oscillation?
//}
return false;
}
void AiCombat::buildNewPath(const MWWorld::Ptr& actor)
{
//Construct path to target
ESM::Pathgrid::Point dest;
dest.mX = mTarget.getRefData().getPosition().pos[0];
dest.mY = mTarget.getRefData().getPosition().pos[1];
dest.mZ = mTarget.getRefData().getPosition().pos[2];
Ogre::Vector3 newPathTarget = Ogre::Vector3(dest.mX, dest.mY, dest.mZ);
float dist = -1; //hack to indicate first time, to construct a new path
Ogre::Vector3 newPathTarget = Ogre::Vector3(mTarget.getRefData().getPosition().pos);
float dist;
if(!mPathFinder.getPath().empty())
{
ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back();
Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ);
dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length());
dist = (newPathTarget - currPathTarget).length();
}
else dist = 1e+38F; // necessarily construct a new path
float targetPosThreshold;
bool isOutside = actor.getCell()->getCell()->isExterior();
if (isOutside)
targetPosThreshold = 300;
else
targetPosThreshold = 100;
float targetPosThreshold = (actor.getCell()->getCell()->isExterior())? 300 : 100;
if((dist < 0) || (dist > targetPosThreshold))
//construct new path only if target has moved away more than on [targetPosThreshold]
if(dist > targetPosThreshold)
{
//construct new path only if target has moved away more than on <targetPosThreshold>
ESM::Position pos = actor.getRefData().getPosition();
ESM::Pathgrid::Point start;
@ -502,17 +594,18 @@ namespace MWMechanics
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
ESM::Pathgrid::Point dest;
dest.mX = newPathTarget.x;
dest.mY = newPathTarget.y;
dest.mZ = newPathTarget.z;
if(!mPathFinder.isPathConstructed())
mPathFinder.buildPath(start, dest, actor.getCell(), isOutside);
mPathFinder.buildPath(start, dest, actor.getCell(), false);
else
{
PathFinder newPathFinder;
newPathFinder.buildPath(start, dest, actor.getCell(), isOutside);
newPathFinder.buildPath(start, dest, actor.getCell(), false);
//TO EXPLORE:
//maybe here is a mistake (?): PathFinder::getPathSize() returns number of grid points in the path,
//not the actual path length. Here we should know if the new path is actually more effective.
//if(pathFinder2.getPathSize() < mPathFinder.getPathSize())
if(!mPathFinder.getPath().empty())
{
newPathFinder.syncStart(mPathFinder.getPath());

@ -39,17 +39,16 @@ namespace MWMechanics
// when mCombatMove is true
float mTimerCombatMove;
// the z rotation angle (degrees) we want to reach
// used every frame when mRotate is true
float mTargetAngle;
// AiCombat states
bool mReadyToAttack, mStrike;
bool mReadyToAttack, mAttack;
bool mFollowTarget;
bool mCombatMove;
bool mBackOffDoor;
bool mRotate;
bool mForceNoShortcut;
ESM::Position mShortcutFailPos;
ESM::Position mLastPos;
MWMechanics::Movement mMovement;
MWWorld::Ptr mTarget;

@ -20,7 +20,7 @@ namespace MWMechanics
TypeIdFollow = 3,
TypeIdActivate = 4,
TypeIdCombat = 5,
TypeIdPersue = 6
TypeIdPursue = 6
};
virtual ~AiPackage();

@ -1,4 +1,4 @@
#include "aipersue.hpp"
#include "aipursue.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
@ -11,15 +11,15 @@
#include "movement.hpp"
#include "creaturestats.hpp"
MWMechanics::AiPersue::AiPersue(const std::string &objectId)
MWMechanics::AiPursue::AiPursue(const std::string &objectId)
: mObjectId(objectId)
{
}
MWMechanics::AiPersue *MWMechanics::AiPersue::clone() const
MWMechanics::AiPursue *MWMechanics::AiPursue::clone() const
{
return new AiPersue(*this);
return new AiPursue(*this);
}
bool MWMechanics::AiPersue::execute (const MWWorld::Ptr& actor, float duration)
bool MWMechanics::AiPursue::execute (const MWWorld::Ptr& actor, float duration)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
ESM::Position pos = actor.getRefData().getPosition();
@ -52,11 +52,13 @@ bool MWMechanics::AiPersue::execute (const MWWorld::Ptr& actor, float duration)
}
}
// 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)
MWWorld::Ptr target = world->getPtr(mObjectId,false);
ESM::Position targetPos = target.getRefData().getPosition();
bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY;
if(!mPathFinder.isPathConstructed() || cellChange)
if(!mPathFinder.isPathConstructed() || cellChange || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
{
mCellX = cell->mData.mX;
mCellY = cell->mData.mY;
@ -76,15 +78,7 @@ bool MWMechanics::AiPersue::execute (const MWWorld::Ptr& actor, float duration)
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]))
(pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 100*100)
{
movement.mPosition[1] = 0;
MWWorld::Ptr target = world->getPtr(mObjectId,false);
@ -100,7 +94,7 @@ bool MWMechanics::AiPersue::execute (const MWWorld::Ptr& actor, float duration)
return false;
}
int MWMechanics::AiPersue::getTypeId() const
int MWMechanics::AiPursue::getTypeId() const
{
return TypeIdPersue;
return TypeIdPursue;
}

@ -1,5 +1,5 @@
#ifndef GAME_MWMECHANICS_AIPERSUE_H
#define GAME_MWMECHANICS_AIPERSUE_H
#ifndef GAME_MWMECHANICS_AIPURSUE_H
#define GAME_MWMECHANICS_AIPURSUE_H
#include "aipackage.hpp"
#include <string>
@ -9,11 +9,11 @@
namespace MWMechanics
{
class AiPersue : public AiPackage
class AiPursue : public AiPackage
{
public:
AiPersue(const std::string &objectId);
virtual AiPersue *clone() const;
AiPursue(const std::string &objectId);
virtual AiPursue *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;

@ -38,7 +38,7 @@ MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& s
copy (sequence);
mDone = sequence.mDone;
}
return *this;
}
@ -51,7 +51,7 @@ int MWMechanics::AiSequence::getTypeId() const
{
if (mPackages.empty())
return -1;
return mPackages.front()->getTypeId();
}
@ -73,9 +73,9 @@ void MWMechanics::AiSequence::stopCombat()
}
}
void MWMechanics::AiSequence::stopPersue()
void MWMechanics::AiSequence::stopPursuit()
{
while (getTypeId() == AiPackage::TypeIdPersue)
while (getTypeId() == AiPackage::TypeIdPursue)
{
delete *mPackages.begin();
mPackages.erase (mPackages.begin());
@ -93,16 +93,21 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration)
{
if (!mPackages.empty())
{
mLastAiPackage = mPackages.front()->getTypeId();
if (mPackages.front()->execute (actor,duration))
MWMechanics::AiPackage* package = mPackages.front();
mLastAiPackage = package->getTypeId();
if (package->execute (actor,duration))
{
delete *mPackages.begin();
mPackages.erase (mPackages.begin());
// To account for the rare case where AiPackage::execute() queued another AI package
// (e.g. AiPursue executing a dialogue script that uses startCombat)
std::list<MWMechanics::AiPackage*>::iterator toRemove =
std::find(mPackages.begin(), mPackages.end(), package);
mPackages.erase(toRemove);
delete package;
mDone = true;
}
else
{
mDone = false;
mDone = false;
}
}
}
@ -116,9 +121,19 @@ void MWMechanics::AiSequence::clear()
mPackages.clear();
}
void MWMechanics::AiSequence::stack (const AiPackage& package)
void MWMechanics::AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor)
{
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); it++)
if (package.getTypeId() == AiPackage::TypeIdCombat || package.getTypeId() == AiPackage::TypeIdPursue)
{
// Notify AiWander of our current position so we can return to it after combat finished
for (std::list<AiPackage *>::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter)
{
if ((*iter)->getTypeId() == AiPackage::TypeIdWander)
static_cast<AiWander*>(*iter)->setReturnPosition(Ogre::Vector3(actor.getRefData().getPosition().pos));
}
}
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); ++it)
{
if(mPackages.front()->getPriority() <= package.getPriority())
{

@ -50,8 +50,8 @@ namespace MWMechanics
void stopCombat();
///< Removes all combat packages until first non-combat or stack empty.
void stopPersue();
///< Removes all persue packages until first non-persue or stack empty.
void stopPursuit();
///< Removes all pursue packages until first non-pursue or stack empty.
bool isPackageDone() const;
///< Has a package been completed during the last update?
@ -62,7 +62,7 @@ namespace MWMechanics
void clear();
///< Remove all packages.
void stack (const AiPackage& package);
void stack (const AiPackage& package, const MWWorld::Ptr& actor);
///< Add \a package to the front of the sequence (suspends current package)
void queue (const AiPackage& package);

@ -1,6 +1,7 @@
#include "aiwander.hpp"
#include <OgreVector3.h>
#include <OgreSceneNode.h>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
@ -37,6 +38,8 @@ namespace MWMechanics
, mRotate(false)
, mTargetAngle(0)
, mSaidGreeting(false)
, mHasReturnPosition(false)
, mReturnPosition(0,0,0)
{
for(unsigned short counter = 0; counter < mIdle.size(); counter++)
{
@ -211,7 +214,7 @@ namespace MWMechanics
// Reduce the turning animation glitch by using a *HUGE* value of
// epsilon... TODO: a proper fix might be in either the physics or the
// animation subsystem
if (zTurn(actor, Ogre::Degree(mTargetAngle), Ogre::Degree(12)))
if (zTurn(actor, Ogre::Degree(mTargetAngle), Ogre::Degree(5)))
mRotate = false;
}
@ -330,21 +333,44 @@ namespace MWMechanics
if(mDistance && cellChange)
mDistance = 0;
if(mChooseAction)
// For stationary NPCs, move back to the starting location if another AiPackage moved us elsewhere
if (cellChange)
mHasReturnPosition = false;
if (mDistance == 0 && mHasReturnPosition && Ogre::Vector3(pos.pos).squaredDistance(mReturnPosition) > 20*20)
{
mPlayedIdle = 0;
unsigned short idleRoll = 0;
mChooseAction = false;
mIdleNow = false;
for(unsigned int counter = 0; counter < mIdle.size(); counter++)
if (!mPathFinder.isPathConstructed())
{
unsigned short idleChance = mIdleChanceMultiplier * mIdle[counter];
unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mIdleChanceMultiplier));
if(randSelect < idleChance && randSelect > idleRoll)
Ogre::Vector3 destNodePos = mReturnPosition;
ESM::Pathgrid::Point dest;
dest.mX = destNodePos[0];
dest.mY = destNodePos[1];
dest.mZ = destNodePos[2];
// actor position is already in world co-ordinates
ESM::Pathgrid::Point start;
start.mX = pos.pos[0];
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
// don't take shortcuts for wandering
mPathFinder.buildPath(start, dest, actor.getCell(), false);
if(mPathFinder.isPathConstructed())
{
mPlayedIdle = counter+2;
idleRoll = randSelect;
mMoveNow = false;
mWalking = true;
}
}
}
if(mChooseAction)
{
mPlayedIdle = 0;
getRandomIdle(); // NOTE: sets mPlayedIdle with a random selection
if(!mPlayedIdle && mDistance)
{
@ -375,7 +401,7 @@ namespace MWMechanics
}
// Allow interrupting a walking actor to trigger a greeting
if(mIdleNow || (mWalking && !mObstacleCheck.isNormalState()))
if(mIdleNow || (mWalking && !mObstacleCheck.isNormalState() && mDistance))
{
// Play a random voice greeting if the player gets too close
int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified();
@ -395,6 +421,8 @@ namespace MWMechanics
mMoveNow = false;
mWalking = false;
mObstacleCheck.clear();
mIdleNow = true;
getRandomIdle();
}
if(!mRotate)
@ -402,11 +430,11 @@ namespace MWMechanics
Ogre::Vector3 dir = playerPos - actorPos;
float length = dir.length();
// FIXME: horrible hack
float faceAngle = Ogre::Radian(Ogre::Math::ACos(dir.y / length) *
((Ogre::Math::ASin(dir.x / length).valueRadians()>0)?1.0:-1.0)).valueDegrees();
float actorAngle = actor.getRefData().getBaseNode()->getOrientation().getRoll().valueDegrees();
// an attempt at reducing the turning animation glitch
if(abs(faceAngle) > 10)
if(abs(abs(faceAngle) - abs(actorAngle)) >= 5) // TODO: is there a better way?
{
mTargetAngle = faceAngle;
mRotate = true;
@ -432,7 +460,8 @@ namespace MWMechanics
}
// Check if idle animation finished
if(!checkIdle(actor, mPlayedIdle))
// FIXME: don't stay forever
if(!checkIdle(actor, mPlayedIdle) && playerDistSqr > helloDistance*helloDistance)
{
mPlayedIdle = 0;
mIdleNow = false;
@ -503,6 +532,7 @@ namespace MWMechanics
mMoveNow = false;
mWalking = false;
mChooseAction = true;
mHasReturnPosition = false;
}
return false; // AiWander package not yet completed
@ -586,5 +616,30 @@ namespace MWMechanics
else
return false;
}
void AiWander::setReturnPosition(const Ogre::Vector3& position)
{
if (!mHasReturnPosition)
{
mHasReturnPosition = true;
mReturnPosition = position;
}
}
void AiWander::getRandomIdle()
{
unsigned short idleRoll = 0;
for(unsigned int counter = 0; counter < mIdle.size(); counter++)
{
unsigned short idleChance = mIdleChanceMultiplier * mIdle[counter];
unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mIdleChanceMultiplier));
if(randSelect < idleChance && randSelect > idleRoll)
{
mPlayedIdle = counter+2;
idleRoll = randSelect;
}
}
}
}

@ -2,8 +2,11 @@
#define GAME_MWMECHANICS_AIWANDER_H
#include "aipackage.hpp"
#include <vector>
#include <OgreVector3.h>
#include "pathfinding.hpp"
#include "obstacle.hpp"
@ -22,10 +25,15 @@ namespace MWMechanics
virtual int getTypeId() const;
///< 0: Wander
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);
void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
void getRandomIdle();
int mDistance; // how far the actor can wander from the spawn point
int mDuration;
@ -38,6 +46,10 @@ namespace MWMechanics
float mGreetDistanceReset;
float mChance;
bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position,
// if we had the actor in the AiWander constructor...
Ogre::Vector3 mReturnPosition;
// Cached current cell location
int mCellX;
int mCellY;

@ -489,12 +489,13 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
mIdleState = CharState_Idle;
}
refreshCurrentAnims(mIdleState, mMovementState, true);
if(mDeathState != CharState_None)
{
playRandomDeath(1.0f);
}
else
refreshCurrentAnims(mIdleState, mMovementState, true);
}
CharacterController::~CharacterController()
@ -1010,6 +1011,7 @@ void CharacterController::update(float duration)
if(mHitState != CharState_None && mJumpState == JumpState_None)
vec = Ogre::Vector3(0.0f);
Ogre::Vector3 rot = cls.getRotationVector(mPtr);
mMovementSpeed = cls.getSpeed(mPtr);
vec.x *= mMovementSpeed;
@ -1175,7 +1177,7 @@ void CharacterController::update(float duration)
}
else
{
if(!(vec.z > 0.0f))
if(!(vec.z > 0.0f))
mJumpState = JumpState_None;
vec.z = 0.0f;

@ -17,7 +17,7 @@ namespace MWMechanics
mAttacked (false), mHostile (false),
mAttackingOrSpell(false),
mIsWerewolf(false),
mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false),
mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false),
mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f)
{
for (int i=0; i<4; ++i)
@ -387,6 +387,8 @@ namespace MWMechanics
void CreatureStats::setKnockedDown(bool value)
{
mKnockdown = value;
if(!value) //Resets the "OverOneFrame" flag
setKnockedDownOverOneFrame(false);
}
bool CreatureStats::getKnockedDown() const
@ -394,6 +396,23 @@ namespace MWMechanics
return mKnockdown;
}
void CreatureStats::setKnockedDownOneFrame(bool value)
{
mKnockdownOneFrame = value;
}
bool CreatureStats::getKnockedDownOneFrame() const
{
return mKnockdownOneFrame;
}
void CreatureStats::setKnockedDownOverOneFrame(bool value) {
mKnockdownOverOneFrame = value;
}
bool CreatureStats::getKnockedDownOverOneFrame() const {
return mKnockdownOverOneFrame;
}
void CreatureStats::setHitRecovery(bool value)
{
mHitRecovery = value;
@ -479,7 +498,7 @@ namespace MWMechanics
}
// Relates to NPC gold reset delay
void CreatureStats::setTradeTime(MWWorld::TimeStamp tradeTime)
void CreatureStats::setTradeTime(MWWorld::TimeStamp tradeTime)
{
mTradeTime = tradeTime;
}
@ -489,11 +508,11 @@ namespace MWMechanics
return mTradeTime;
}
void CreatureStats::setGoldPool(int pool)
void CreatureStats::setGoldPool(int pool)
{
mGoldPool = pool;
}
int CreatureStats::getGoldPool() const
int CreatureStats::getGoldPool() const
{
return mGoldPool;
}

@ -41,6 +41,8 @@ namespace MWMechanics
bool mHostile;
bool mAttackingOrSpell;
bool mKnockdown;
bool mKnockdownOneFrame;
bool mKnockdownOverOneFrame;
bool mHitRecovery;
bool mBlock;
unsigned int mMovementFlags;
@ -188,7 +190,14 @@ namespace MWMechanics
float getEvasion() const;
void setKnockedDown(bool value);
///Returns true for the entire duration of the actor being knocked down
bool getKnockedDown() const;
void setKnockedDownOneFrame(bool value);
///Returns true only for the first frame of the actor being knocked out; used for "onKnockedOut" command
bool getKnockedDownOneFrame() const;
void setKnockedDownOverOneFrame(bool value);
///Returns true for all but the first frame of being knocked out; used to know to not reset mKnockedDownOneFrame
bool getKnockedDownOverOneFrame() const;
void setHitRecovery(bool value);
bool getHitRecovery() const;
void setBlock(bool value);

@ -298,13 +298,15 @@ namespace MWMechanics
if(stats.getTimeToStartDrowning() != mWatchedStats.getTimeToStartDrowning())
{
const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fHoldBreathTime")->getFloat();
mWatchedStats.setTimeToStartDrowning(stats.getTimeToStartDrowning());
if(stats.getTimeToStartDrowning() >= 20.0f)
if(stats.getTimeToStartDrowning() >= fHoldBreathTime)
winMgr->setDrowningBarVisibility(false);
else
{
winMgr->setDrowningBarVisibility(true);
winMgr->setDrowningTimeLeft(stats.getTimeToStartDrowning());
winMgr->setDrowningTimeLeft(stats.getTimeToStartDrowning(), fHoldBreathTime);
}
}
@ -338,6 +340,8 @@ namespace MWMechanics
MWWorld::ContainerStoreIterator enchantItem = inv.getSelectedEnchantItem();
if (enchantItem != inv.end())
winMgr->setSelectedEnchantItem(*enchantItem);
else if (winMgr->getSelectedSpell() == "")
winMgr->unsetSelectedSpell();
}
if (mUpdatePlayer)
@ -856,7 +860,8 @@ namespace MWMechanics
// Find an actor who witnessed the crime
for (std::vector<MWWorld::Ptr>::iterator it = neighbors.begin(); it != neighbors.end(); ++it)
{
if (*it == ptr) continue; // not the player
if ( *it == ptr
|| !it->getClass().isNpc()) continue; // not the player and is an NPC
// Was the crime seen?
if ( ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) ||
@ -872,7 +877,8 @@ namespace MWMechanics
// Tell everyone, including yourself
for (std::vector<MWWorld::Ptr>::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1)
{
if (*it1 == ptr) continue; // not the player
if ( *it == ptr
|| !it->getClass().isNpc()) continue; // not the player and is an NPC
// TODO: Add more messages
if (type == OT_Theft)

@ -325,22 +325,22 @@ namespace MWMechanics
}
// used by AiCombat, see header for the rationale
void PathFinder::syncStart(const std::list<ESM::Pathgrid::Point> &path)
bool PathFinder::syncStart(const std::list<ESM::Pathgrid::Point> &path)
{
if (mPath.size() < 2)
return; //nothing to pop
return false; //nothing to pop
std::list<ESM::Pathgrid::Point>::const_iterator oldStart = path.begin();
std::list<ESM::Pathgrid::Point>::iterator iter = ++mPath.begin();
if( (*iter).mX == oldStart->mX
&& (*iter).mY == oldStart->mY
&& (*iter).mZ == oldStart->mZ
&& (*iter).mAutogenerated == oldStart->mAutogenerated
&& (*iter).mConnectionNum == oldStart->mConnectionNum )
&& (*iter).mZ == oldStart->mZ)
{
mPath.pop_front();
return true;
}
return false;
}
}

@ -57,16 +57,20 @@ namespace MWMechanics
return mPath.size();
}
std::list<ESM::Pathgrid::Point> getPath() const
const std::list<ESM::Pathgrid::Point>& getPath() const
{
return mPath;
}
// When first point of newly created path is the nearest to actor point,
// then a situation can occure when this point is undesirable
// (if the 2nd point of new path == the 1st point of old path)
// This functions deletes that point.
void syncStart(const std::list<ESM::Pathgrid::Point> &path);
/** Synchronize new path with old one to avoid visiting 1 waypoint 2 times
@note
If the first point is chosen as the nearest one
the situation can occur when the 1st point of the new path is undesirable
(i.e. the 2nd point of new path == the 1st point of old path).
@param path - old path
@return true if such point was found and deleted
*/
bool syncStart(const std::list<ESM::Pathgrid::Point> &path);
void addPointToPath(ESM::Pathgrid::Point &point)
{

@ -296,7 +296,7 @@ namespace MWMechanics
// add this edge to openset, lowest cost goes to the front
// TODO: if this causes performance problems a hash table may help
std::list<int>::iterator it = openset.begin();
for(it = openset.begin(); it!= openset.end(); it++)
for(it = openset.begin(); it!= openset.end(); ++it)
{
if(fScore[*it] > fScore[dest])
break;

@ -401,10 +401,10 @@ namespace MWMechanics
if (!exploded)
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, mTarget, effects, caster, mId, mSourceName);
if (reflectedEffects.mList.size())
if (!reflectedEffects.mList.empty())
inflict(caster, target, reflectedEffects, range, true);
if (appliedLastingEffects.size())
if (!appliedLastingEffects.empty())
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects,
mSourceName, caster.getRefData().getHandle());

@ -129,7 +129,7 @@ namespace MWMechanics
if (spell->mData.mType == ESM::Spell::ST_Disease)
mSpells.erase(iter++);
else
iter++;
++iter;
}
}
@ -143,7 +143,7 @@ namespace MWMechanics
if (spell->mData.mType == ESM::Spell::ST_Blight)
mSpells.erase(iter++);
else
iter++;
++iter;
}
}
@ -157,7 +157,7 @@ namespace MWMechanics
if (Misc::StringUtils::ciEqual(spell->mId, "corprus"))
mSpells.erase(iter++);
else
iter++;
++iter;
}
}
@ -171,7 +171,7 @@ namespace MWMechanics
if (spell->mData.mType == ESM::Spell::ST_Curse)
mSpells.erase(iter++);
else
iter++;
++iter;
}
}

@ -10,9 +10,9 @@
namespace MWMechanics
{
bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree epsilon)
bool smoothTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, int axis, Ogre::Degree epsilon)
{
Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[2]);
Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[axis]);
Ogre::Radian diff (targetAngle - currentAngle);
if (diff >= Ogre::Degree(180))
{
@ -30,13 +30,17 @@ bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree eps
if (absDiff < epsilon)
return true;
// Max. speed of 10 radian per sec
Ogre::Radian limit = Ogre::Radian(10) * MWBase::Environment::get().getFrameDuration();
Ogre::Radian limit = MAX_VEL_ANGULAR * MWBase::Environment::get().getFrameDuration();
if (absDiff > limit)
diff = Ogre::Math::Sign(diff) * limit;
actor.getClass().getMovementSettings(actor).mRotation[2] = diff.valueRadians();
actor.getClass().getMovementSettings(actor).mRotation[axis] = diff.valueRadians();
return false;
}
bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree epsilon)
{
return smoothTurn(actor, targetAngle, 2, epsilon);
}
}

@ -10,11 +10,17 @@ class Ptr;
namespace MWMechanics
{
// Max rotating speed, radian/sec
const Ogre::Radian MAX_VEL_ANGULAR(10);
/// configure rotation settings for an actor to reach this target angle (eventually)
/// @return have we reached the target angle?
bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle,
Ogre::Degree epsilon = Ogre::Degree(0.5));
bool smoothTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, int axis,
Ogre::Degree epsilon = Ogre::Degree(0.5));
}
#endif

@ -411,7 +411,7 @@ Ogre::Node *Animation::getNode(const std::string &name)
NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::TextKeyMap &keys, const std::string &groupname)
{
NifOgre::TextKeyMap::const_iterator iter(keys.begin());
for(;iter != keys.end();iter++)
for(;iter != keys.end();++iter)
{
if(iter->second.compare(0, groupname.size(), groupname) == 0 &&
iter->second.compare(groupname.size(), 2, ": ") == 0)
@ -424,7 +424,7 @@ NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::Tex
bool Animation::hasAnimation(const std::string &anim)
{
AnimSourceList::const_iterator iter(mAnimSources.begin());
for(;iter != mAnimSources.end();iter++)
for(;iter != mAnimSources.end();++iter)
{
const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys;
if(findGroupStart(keys, anim) != keys.end())
@ -465,7 +465,7 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node
stoptime = keyiter->first;
break;
}
keyiter++;
++keyiter;
}
if(stoptime > starttime)
@ -585,13 +585,13 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
std::string starttag = groupname+": "+start;
NifOgre::TextKeyMap::const_iterator startkey(groupstart);
while(startkey != keys.end() && startkey->second != starttag)
startkey++;
++startkey;
if(startkey == keys.end() && start == "loop start")
{
starttag = groupname+": start";
startkey = groupstart;
while(startkey != keys.end() && startkey->second != starttag)
startkey++;
++startkey;
}
if(startkey == keys.end())
return false;
@ -603,7 +603,7 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
// The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop".
// Why, just why? :(
&& (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag))
stopkey++;
++stopkey;
if(stopkey == keys.end())
return false;
@ -627,7 +627,7 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
state.mLoopStartTime = key->first;
else if(key->second == loopstoptag)
state.mLoopStopTime = key->first;
key++;
++key;
}
}
@ -776,7 +776,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo
/* Look in reverse; last-inserted source has priority. */
AnimSourceList::reverse_iterator iter(mAnimSources.rbegin());
for(;iter != mAnimSources.rend();iter++)
for(;iter != mAnimSources.rend();++iter)
{
const NifOgre::TextKeyMap &textkeys = (*iter)->mTextKeys;
AnimState state;
@ -795,7 +795,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo
while(textkey != textkeys.end() && textkey->first <= state.mTime)
{
handleTextKey(state, groupname, textkey);
textkey++;
++textkey;
}
if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0)
@ -810,7 +810,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo
while(textkey != textkeys.end() && textkey->first <= state.mTime)
{
handleTextKey(state, groupname, textkey);
textkey++;
++textkey;
}
}
@ -965,7 +965,7 @@ Ogre::Vector3 Animation::runAnimation(float duration)
while(textkey != textkeys.end() && textkey->first <= state.mTime)
{
handleTextKey(state, stateiter->first, textkey);
textkey++;
++textkey;
}
if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0)
@ -979,7 +979,7 @@ Ogre::Vector3 Animation::runAnimation(float duration)
while(textkey != textkeys.end() && textkey->first <= state.mTime)
{
handleTextKey(state, stateiter->first, textkey);
textkey++;
++textkey;
}
if(state.mTime >= state.mLoopStopTime)

@ -34,6 +34,10 @@ namespace MWRender
virtual void rebuild();
private:
CharacterPreview(const CharacterPreview&);
CharacterPreview& operator=(const CharacterPreview&);
protected:
virtual bool renderHeadOnly() { return false; }

@ -106,7 +106,7 @@ ManualObject *Debugging::createPathgridPoints(const ESM::Pathgrid *pathgrid)
uint32 startIndex = 0;
for(ESM::Pathgrid::PointList::const_iterator it = pathgrid->mPoints.begin();
it != pathgrid->mPoints.end();
it++, startIndex += 6)
++it, startIndex += 6)
{
Vector3 pointPos(it->mX, it->mY, it->mZ);

@ -240,25 +240,25 @@ Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::CellStore* cell)
void Objects::enableLights()
{
PtrAnimationMap::const_iterator it = mObjects.begin();
for(;it != mObjects.end();it++)
for(;it != mObjects.end();++it)
it->second->enableLights(true);
}
void Objects::disableLights()
{
PtrAnimationMap::const_iterator it = mObjects.begin();
for(;it != mObjects.end();it++)
for(;it != mObjects.end();++it)
it->second->enableLights(false);
}
void Objects::update(float dt, Ogre::Camera* camera)
{
PtrAnimationMap::const_iterator it = mObjects.begin();
for(;it != mObjects.end();it++)
for(;it != mObjects.end();++it)
it->second->runAnimation(dt);
it = mObjects.begin();
for(;it != mObjects.end();it++)
for(;it != mObjects.end();++it)
it->second->preRender(camera);
}

@ -563,7 +563,8 @@ void RenderingManager::configureAmbient(MWWorld::CellStore &mCell)
Ogre::ColourValue colour;
colour.setAsABGR (mCell.getCell()->mAmbi.mSunlight);
mSun->setDiffuseColour (colour);
mSun->setDirection(0,-1,0);
mSun->setDirection(1,-1,-1);
sunEnable(false);
}
}
// Switch through lighting modes.

@ -28,7 +28,9 @@ RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager)
mRippleAreaLength(1000),
mImpulseSize(20),
mTexelOffset(0,0),
mFirstUpdate(true)
mFirstUpdate(true),
mRectangle(NULL),
mImpulse(NULL)
{
Ogre::AxisAlignedBox aabInf;
aabInf.setInfinite();
@ -105,6 +107,7 @@ RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager)
RippleSimulation::~RippleSimulation()
{
delete mRectangle;
delete mImpulse;
Ogre::Root::getSingleton().destroySceneManager(mSceneMgr);
}

@ -6,14 +6,9 @@
#include <cstdio>
#include <cmath>
#include <OgreRoot.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreRenderWindow.h>
#include <OgreTextureManager.h>
#include <OgreTechnique.h>
#include <OgreRectangle2D.h>
#include <OgreMaterialManager.h>
#include <OgreSceneNode.h>
#include <OgreStringConverter.h>
#include <boost/thread.hpp>
@ -21,9 +16,6 @@
#include "../mwbase/soundmanager.hpp"
#include "../mwsound/sound_decoder.hpp"
#include "../mwsound/sound.hpp"
#include "../mwbase/inputmanager.hpp"
#include "renderconst.hpp"
#ifdef _WIN32
#include <BaseTsd.h>

@ -48,7 +48,7 @@ namespace MWScript
for (unsigned int i=0; i<arg0; ++i) runtime.pop();
MWMechanics::AiActivate activatePackage(objectID);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(activatePackage);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(activatePackage, ptr);
std::cout << "AiActivate" << std::endl;
}
};
@ -75,7 +75,7 @@ namespace MWScript
for (unsigned int i=0; i<arg0; ++i) runtime.pop();
MWMechanics::AiTravel travelPackage(x, y, z);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(travelPackage);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(travelPackage, ptr);
std::cout << "AiTravel: " << x << ", " << y << ", " << z << std::endl;
}
@ -109,7 +109,7 @@ namespace MWScript
for (unsigned int i=0; i<arg0; ++i) runtime.pop();
MWMechanics::AiEscort escortPackage(actorID, duration, x, y, z);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(escortPackage);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr);
std::cout << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration
<< std::endl;
@ -147,7 +147,7 @@ namespace MWScript
for (unsigned int i=0; i<arg0; ++i) runtime.pop();
MWMechanics::AiEscort escortPackage(actorID, cellID, duration, x, y, z);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(escortPackage);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr);
std::cout << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration
<< std::endl;
@ -211,7 +211,7 @@ namespace MWScript
for (unsigned int i=0; i<arg0; ++i) runtime.pop();
MWMechanics::AiWander wanderPackage(range, duration, time, idleList, repeat);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(wanderPackage);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(wanderPackage, ptr);
}
};
@ -299,7 +299,7 @@ namespace MWScript
for (unsigned int i=0; i<arg0; ++i) runtime.pop();
MWMechanics::AiFollow followPackage(actorID, duration, x, y ,z);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(followPackage);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(followPackage, ptr);
std::cout << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration
<< std::endl;
@ -337,7 +337,7 @@ namespace MWScript
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);
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(followPackage, ptr);
std::cout << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration
<< std::endl;
}
@ -440,7 +440,7 @@ namespace MWScript
creatureStats.setHostile(true);
creatureStats.getAiSequence().stack(
MWMechanics::AiCombat(MWBase::Environment::get().getWorld()->getPtr(targetID, true) ));
MWMechanics::AiCombat(MWBase::Environment::get().getWorld()->getPtr(targetID, true) ), actor);
}
};

@ -388,5 +388,7 @@ op 0x200023c: StopCombat
op 0x200023d: StopCombatExplicit
op 0x200023e: GetPcInJail
op 0x200023f: GetPcTraveling
op 0x2000240: onKnockout
op 0x2000241: onKnockoutExplicit
opcodes 0x2000240-0x3ffffff unused
opcodes 0x2000242-0x3ffffff unused

@ -97,7 +97,7 @@ namespace MWScript
return mScripts.size();
}
void GlobalScripts::write (ESM::ESMWriter& writer) const
void GlobalScripts::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
{
for (std::map<std::string, std::pair<bool, Locals> >::const_iterator iter (mScripts.begin());
iter!=mScripts.end(); ++iter)
@ -113,6 +113,7 @@ namespace MWScript
writer.startRecord (ESM::REC_GSCR);
script.save (writer);
writer.endRecord (ESM::REC_GSCR);
progress.increaseProgress();
}
}

@ -14,6 +14,11 @@ namespace ESM
class ESMReader;
}
namespace Loading
{
class Listener;
}
namespace MWWorld
{
struct ESMStore;
@ -46,7 +51,7 @@ namespace MWScript
int countSavedGameRecords() const;
void write (ESM::ESMWriter& writer) const;
void write (ESM::ESMWriter& writer, Loading::Listener& progress) const;
bool readRecord (ESM::ESMReader& reader, int32_t type);
///< Records for variables that do not exist are dropped silently.

@ -302,15 +302,23 @@ namespace MWScript
std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first;
std::map<std::string, int> ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks();
std::map<std::string, int>::const_iterator it = ranks.begin();
std::map<std::string, int>::const_iterator it = ranks.find(factionId);
int rank = -1;
if (it != ranks.end())
rank = it->second;
// If you are not in the faction, PcRank returns the first rank, for whatever reason.
// This is used by the dialogue when joining the Thieves Guild in Balmora.
if (rank == -1)
rank = 0;
const MWWorld::ESMStore &store = world->getStore();
const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId);
if(it->second < 0 || it->second > 9) // there are only 10 ranks
if(rank < 0 || rank > 9) // there are only 10 ranks
return "";
return faction->mRanks[it->second];
return faction->mRanks[rank];
}
std::string InterpreterContext::getPCNextRank() const
@ -320,25 +328,25 @@ namespace MWScript
std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first;
const MWWorld::ESMStore &store = world->getStore();
const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId);
std::map<std::string, int> ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks();
std::map<std::string, int>::const_iterator it = ranks.find(factionId);
int rank = -1;
if (it != ranks.end())
rank = it->second;
if (!ranks.empty())
{
std::map<std::string, int>::const_iterator it = ranks.begin();
++rank; // Next rank
if(it->second < -1 || it->second > 9)
return "";
// if we are already at max rank, there is no next rank
if (rank > 9)
rank = 9;
if(it->second <= 8) // If player is at max rank, there is no next rank
return faction->mRanks[it->second + 1];
else
return faction->mRanks[it->second];
}
else
return faction->mRanks[0];
const MWWorld::ESMStore &store = world->getStore();
const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId);
if(rank < 0 || rank > 9)
return "";
return faction->mRanks[rank];
}
int InterpreterContext::getPCBounty() const
@ -370,10 +378,14 @@ namespace MWScript
float InterpreterContext::getDistance (const std::string& name, const std::string& id) const
{
// TODO handle exterior cells (when ref and ref2 are located in different cells)
const MWWorld::Ptr ref2 = getReference (id, false);
const MWWorld::Ptr ref2 = getReference (id, false, false);
// If either actor is in a non-active cell, return a large value (just like vanilla)
if (ref2.isEmpty())
return std::numeric_limits<float>().max();
const MWWorld::Ptr ref = MWBase::Environment::get().getWorld()->getPtr (name, true);
const MWWorld::Ptr ref = getReference (name, false, false);
if (ref.isEmpty())
return std::numeric_limits<float>().max();
double diff[3];

@ -1060,6 +1060,22 @@ namespace MWScript
}
};
template <class R>
class OpOnKnockout : public Interpreter::Opcode0
{
public:
virtual void execute (Interpreter::Runtime& runtime)
{
MWWorld::Ptr ptr = R()(runtime);
Interpreter::Type_Integer value =
MWWorld::Class::get (ptr).getCreatureStats (ptr).getKnockedDownOneFrame();
runtime.push (value);
}
};
template <class R>
class OpIsWerewolf : public Interpreter::Opcode0
{
@ -1236,6 +1252,8 @@ namespace MWScript
interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeOnDeathExplicit, new OpOnDeath<ExplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockout, new OpOnKnockout<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockoutExplicit, new OpOnKnockout<ExplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolf, new OpIsWerewolf<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolfExplicit, new OpIsWerewolf<ExplicitRef>);
@ -1245,7 +1263,7 @@ namespace MWScript
interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolf, new OpSetWerewolf<ImplicitRef, false>);
interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolfExplicit, new OpSetWerewolf<ExplicitRef, false>);
interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobatics, new OpSetWerewolfAcrobatics<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics<ExplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics<ExplicitRef>);
}
}
}

@ -442,7 +442,7 @@ namespace MWSound
{
snditer->first->setFadeout(duration);
}
snditer++;
++snditer;
}
}

@ -95,6 +95,21 @@ MWState::Character::Character (const boost::filesystem::path& saves, const std::
}
}
void MWState::Character::cleanup()
{
if (mSlots.size() == 0)
{
// All slots are gone, no need to keep the empty directory
if (boost::filesystem::is_directory (mPath))
{
// Extra safety check to make sure the directory is empty (e.g. slots failed to parse header)
boost::filesystem::directory_iterator it(mPath);
if (it == boost::filesystem::directory_iterator())
boost::filesystem::remove_all(mPath);
}
}
}
const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profile)
{
addSlot (profile);
@ -102,6 +117,21 @@ const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profi
return &mSlots.back();
}
void MWState::Character::deleteSlot (const Slot *slot)
{
int index = slot - &mSlots[0];
if (index<0 || index>=static_cast<int> (mSlots.size()))
{
// sanity check; not entirely reliable
throw std::logic_error ("slot not found");
}
boost::filesystem::remove(slot->mPath);
mSlots.erase (mSlots.begin()+index);
}
const MWState::Slot *MWState::Character::updateSlot (const Slot *slot, const ESM::SavedGame& profile)
{
int index = slot - &mSlots[0];
@ -150,4 +180,4 @@ ESM::SavedGame MWState::Character::getSignature() const
slot = *iter;
return slot.mProfile;
}
}

@ -36,11 +36,19 @@ namespace MWState
Character (const boost::filesystem::path& saves, const std::string& game);
void cleanup();
///< Delete the directory we used, if it is empty
const Slot *createSlot (const ESM::SavedGame& profile);
///< Create new slot.
///
/// \attention The ownership of the slot is not transferred.
/// \note Slot must belong to this character.
///
/// \attention The \a slot pointer will be invalidated by this call.
void deleteSlot (const Slot *slot);
const Slot *updateSlot (const Slot *slot, const ESM::SavedGame& profile);
/// \note Slot must belong to this character.
///

@ -47,6 +47,25 @@ MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create)
return mCurrent;
}
void MWState::CharacterManager::deleteSlot(const MWState::Character *character, const MWState::Slot *slot)
{
int index = character - &mCharacters[0];
if (index<0 || index>=static_cast<int> (mCharacters.size()))
throw std::logic_error ("invalid character");
mCharacters[index].deleteSlot(slot);
if (mCharacters[index].begin() == mCharacters[index].end())
{
// All slots deleted, cleanup and remove this character
mCharacters[index].cleanup();
if (character == mCurrent)
mCurrent = NULL;
mCharacters.erase(mCharacters.begin() + index);
}
}
void MWState::CharacterManager::createCharacter()
{
std::ostringstream stream;

@ -30,6 +30,8 @@ namespace MWState
Character *getCurrentCharacter (bool create = true);
///< \param create Create a new character, if there is no current character.
void deleteSlot(const MWState::Character *character, const MWState::Slot *slot);
void createCharacter();
///< Create new character within saved game management

@ -196,26 +196,36 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0
writer.setFormat (ESM::Header::CurrentFormat);
writer.setRecordCount (
1 // saved game header
+MWBase::Environment::get().getJournal()->countSavedGameRecords()
+MWBase::Environment::get().getWorld()->countSavedGameRecords()
+MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords()
+MWBase::Environment::get().getDialogueManager()->countSavedGameRecords()
+1 // global map
);
int recordCount = 1 // saved game header
+MWBase::Environment::get().getJournal()->countSavedGameRecords()
+MWBase::Environment::get().getWorld()->countSavedGameRecords()
+MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords()
+MWBase::Environment::get().getDialogueManager()->countSavedGameRecords()
+MWBase::Environment::get().getWindowManager()->countSavedGameRecords();
writer.setRecordCount (recordCount);
writer.save (stream);
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
listener.setProgressRange(recordCount);
listener.setLabel("#{sNotifyMessage4}");
Loading::ScopedLoad load(&listener);
writer.startRecord (ESM::REC_SAVE);
slot->mProfile.save (writer);
writer.endRecord (ESM::REC_SAVE);
listener.increaseProgress();
MWBase::Environment::get().getJournal()->write (writer);
MWBase::Environment::get().getDialogueManager()->write (writer);
MWBase::Environment::get().getWorld()->write (writer);
MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer);
MWBase::Environment::get().getWindowManager()->write(writer);
MWBase::Environment::get().getJournal()->write (writer, listener);
MWBase::Environment::get().getDialogueManager()->write (writer, listener);
MWBase::Environment::get().getWorld()->write (writer, listener);
MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener);
MWBase::Environment::get().getWindowManager()->write(writer, listener);
// Ensure we have written the number of records that was estimated
if (writer.getRecordCount() != recordCount+1) // 1 extra for TES3 record
std::cerr << "Warning: number of written savegame records does not match. Estimated: " << recordCount+1 << ", written: " << writer.getRecordCount() << std::endl;
writer.close();
@ -225,8 +235,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
void MWState::StateManager::quickSave (std::string name)
{
if (mState!=State_Running ||
MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")!=-1) // char gen
if (!(mState==State_Running &&
MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1 // char gen
&& MWBase::Environment::get().getWindowManager()->isSavingAllowed()))
{
//You can not save your game right now
MWBase::Environment::get().getWindowManager()->messageBox("#{sSaveGameDenied}");
@ -243,8 +254,6 @@ void MWState::StateManager::quickSave (std::string name)
slot = &*it;
}
MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage4}");
saveGame(name, slot);
}
@ -261,6 +270,13 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl
std::map<int, int> contentFileMap = buildContentFileIndexMap (reader);
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
listener.setProgressRange(reader.getRecordCount());
listener.setLabel("#{sLoadingMessage14}");
Loading::ScopedLoad load(&listener);
while (reader.hasMoreRecs())
{
ESM::NAME n = reader.getRecName();
@ -308,7 +324,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl
break;
case ESM::REC_GMAP:
case ESM::REC_KEYS:
MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val);
break;
@ -318,6 +334,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl
/// \todo log error
reader.skipRecord();
}
listener.increaseProgress();
}
mCharacterManager.setCurrentCharacter(character);
@ -350,10 +367,12 @@ void MWState::StateManager::quickLoad()
{
if (Character* mCurrentCharacter = getCurrentCharacter (false))
if (const MWState::Slot* slot = &*mCurrentCharacter->begin()) //Get newest save
{
//MWBase::Environment::get().getWindowManager()->messageBox("#{sLoadingMessage14}"); //it overlaps
loadGame (mCurrentCharacter, slot);
}
}
void MWState::StateManager::deleteGame(const MWState::Character *character, const MWState::Slot *slot)
{
mCharacterManager.deleteSlot(character, slot);
}
MWState::Character *MWState::StateManager::getCurrentCharacter (bool create)

@ -44,6 +44,9 @@ namespace MWState
virtual void endGame();
virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot);
///< Delete a saved game slot from this character. If all save slots are deleted, the character will be deleted too.
virtual void saveGame (const std::string& description, const Slot *slot = 0);
///< Write a saved game to \a slot or create a new slot if \a slot == 0.
///

@ -3,7 +3,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/player.hpp"
@ -24,7 +23,12 @@ namespace MWWorld
void ActionRead::executeImp (const MWWorld::Ptr& actor) {
if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { //Ensure we're not in combat
//Ensure we're not in combat
if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()
// Reading in combat is still allowed if the scroll/book is not in the player inventory yet
// (since otherwise, there would be no way to pick it up)
&& getTarget().getContainerStore() == &actor.getClass().getContainerStore(actor)
) {
MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage4}");
return;
}

@ -20,7 +20,7 @@ namespace MWWorld
//find any NPC that is following the actor and teleport him too
std::list<MWWorld::Ptr> followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor);
for(std::list<MWWorld::Ptr>::iterator it = followers.begin();it != followers.end();it++)
for(std::list<MWWorld::Ptr>::iterator it = followers.begin();it != followers.end();++it)
{
std::cout << "teleporting someone!" << (*it).getCellRef().mRefID;
executeImp(*it);

@ -277,17 +277,23 @@ int MWWorld::Cells::countSavedGameRecords() const
return count;
}
void MWWorld::Cells::write (ESM::ESMWriter& writer) const
void MWWorld::Cells::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
{
for (std::map<std::pair<int, int>, CellStore>::iterator iter (mExteriors.begin());
iter!=mExteriors.end(); ++iter)
if (iter->second.hasState())
{
writeCell (writer, iter->second);
progress.increaseProgress(); // Assumes that each cell writes one record
}
for (std::map<std::string, CellStore>::iterator iter (mInteriors.begin());
iter!=mInteriors.end(); ++iter)
if (iter->second.hasState())
{
writeCell (writer, iter->second);
progress.increaseProgress(); // Assumes that each cell writes one record
}
}
bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, int32_t type,

@ -15,6 +15,11 @@ namespace ESM
struct Cell;
}
namespace Loading
{
class Listener;
}
namespace MWWorld
{
class ESMStore;
@ -69,7 +74,7 @@ namespace MWWorld
int countSavedGameRecords() const;
void write (ESM::ESMWriter& writer) const;
void write (ESM::ESMWriter& writer, Loading::Listener& progress) const;
bool readRecord (ESM::ESMReader& reader, int32_t type,
const std::map<int, int>& contentFileMap);

@ -433,7 +433,6 @@ namespace MWWorld
while(mCell->getNextRef(esm[index], ref, deleted))
{
// Don't load reference if it was moved to a different cell.
std::string lowerCase = Misc::StringUtils::lowerCase(ref.mRefID);
ESM::MovedCellRefTracker::const_iterator iter =
std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum);
if (iter != mCell->mMovedRefs.end()) {

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

Loading…
Cancel
Save