merge
This commit is contained in:
graffy76 2014-05-03 19:16:42 -05:00
commit 35d1502308
142 changed files with 1429 additions and 656 deletions

View file

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

View file

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

View file

@ -124,7 +124,7 @@ void printEffectList(ESM::EffectList effects)
{ {
int i = 0; int i = 0;
std::vector<ESM::ENAMstruct>::iterator eit; 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) std::cout << " Effect[" << i << "]: " << magicEffectLabel(eit->mEffectID)
<< " (" << eit->mEffectID << ")" << std::endl; << " (" << eit->mEffectID << ")" << std::endl;

View file

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

View file

@ -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 //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) 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); mFileDialog.addFiles(path);
} }
/* /*

View file

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

4
apps/opencs/view/world/datadisplaydelegate.cpp Executable file → Normal file
View file

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

View file

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

View file

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

View file

@ -67,7 +67,7 @@ add_openmw_dir (mwclass
add_openmw_dir (mwmechanics add_openmw_dir (mwmechanics
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects 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 aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
disease pickpocket levelledlist combat steering obstacle disease pickpocket levelledlist combat steering obstacle
) )

View file

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

View file

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

View file

@ -55,6 +55,8 @@ namespace MWBase
virtual void endGame() = 0; 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; 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. ///< Write a saved game to \a slot or create a new slot if \a slot == 0.
/// ///

View file

@ -156,8 +156,9 @@ namespace MWBase
virtual void setValue (const std::string& id, int value) = 0; virtual void setValue (const std::string& id, int value) = 0;
/// Set time left for the player to start drowning (update the drowning bar) /// Set time left for the player to start drowning (update the drowning bar)
/// @param time value from [0,20] /// @param time time left to start drowning
virtual void setDrowningTimeLeft (float time) =0; /// @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; virtual void setPlayerClass (const ESM::Class &class_) = 0;
///< set current class of player ///< set current class of player
@ -302,8 +303,12 @@ namespace MWBase
/// Clear all savegame-specific data /// Clear all savegame-specific data
virtual void clear() = 0; 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 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;
}; };
} }

View file

@ -108,7 +108,7 @@ namespace MWBase
virtual int countSavedGameRecords() const = 0; 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, virtual void readRecord (ESM::ESMReader& reader, int32_t type,
const std::map<int, int>& contentFileMap) = 0; 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; virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0;
///< get Line of Sight (morrowind stupid implementation) ///< 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 void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0;
virtual int canRest() = 0; virtual int canRest() = 0;

View file

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

View file

@ -170,6 +170,14 @@ namespace MWClass
virtual int getBaseGold(const MWWorld::Ptr& ptr) const; virtual int getBaseGold(const MWWorld::Ptr& ptr) const;
virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) 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;
}
}; };
} }

View file

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

View file

@ -83,7 +83,7 @@ namespace MWDialogue
virtual int countSavedGameRecords() const; 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); virtual void readRecord (ESM::ESMReader& reader, int32_t type);
}; };

View file

@ -167,7 +167,7 @@ namespace MWDialogue
return count; 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) for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter)
{ {
@ -178,6 +178,7 @@ namespace MWDialogue
writer.startRecord (ESM::REC_QUES); writer.startRecord (ESM::REC_QUES);
state.save (writer); state.save (writer);
writer.endRecord (ESM::REC_QUES); writer.endRecord (ESM::REC_QUES);
progress.increaseProgress();
for (Topic::TEntryIter iter (quest.begin()); iter!=quest.end(); ++iter) for (Topic::TEntryIter iter (quest.begin()); iter!=quest.end(); ++iter)
{ {
@ -188,6 +189,7 @@ namespace MWDialogue
writer.startRecord (ESM::REC_JOUR); writer.startRecord (ESM::REC_JOUR);
entry.save (writer); entry.save (writer);
writer.endRecord (ESM::REC_JOUR); writer.endRecord (ESM::REC_JOUR);
progress.increaseProgress();
} }
} }
@ -199,6 +201,7 @@ namespace MWDialogue
writer.startRecord (ESM::REC_JOUR); writer.startRecord (ESM::REC_JOUR);
entry.save (writer); entry.save (writer);
writer.endRecord (ESM::REC_JOUR); writer.endRecord (ESM::REC_JOUR);
progress.increaseProgress();
} }
for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter) for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter)
@ -214,6 +217,7 @@ namespace MWDialogue
writer.startRecord (ESM::REC_JOUR); writer.startRecord (ESM::REC_JOUR);
entry.save (writer); entry.save (writer);
writer.endRecord (ESM::REC_JOUR); writer.endRecord (ESM::REC_JOUR);
progress.increaseProgress();
} }
} }
} }

View file

@ -64,7 +64,7 @@ namespace MWDialogue
virtual int countSavedGameRecords() const; 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); virtual void readRecord (ESM::ESMReader& reader, int32_t type);
}; };

View file

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

View file

@ -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()) if (mActor.getClass().isNpc())
{ {
@ -18,7 +18,7 @@ namespace MWGui
stats.modifyProfit(MWWorld::Class::get(item.mBase).getValue(item.mBase) * count); 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) void CompanionItemModel::removeItem (const ItemStack& item, size_t count)

View file

@ -13,7 +13,7 @@ namespace MWGui
public: public:
CompanionItemModel (const MWWorld::Ptr& actor); 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); virtual void removeItem (const ItemStack& item, size_t count);
}; };

View file

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

View file

@ -13,6 +13,7 @@
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "../mwmechanics/pickpocket.hpp" #include "../mwmechanics/pickpocket.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "countdialog.hpp" #include "countdialog.hpp"
#include "tradewindow.hpp" #include "tradewindow.hpp"
@ -84,8 +85,7 @@ namespace MWGui
// otherwise, do the transfer // otherwise, do the transfer
if (targetModel != mSourceModel) if (targetModel != mSourceModel)
{ {
targetModel->copyItem(mItem, mDraggedCount); mSourceModel->moveItem(mItem, mDraggedCount, targetModel);
mSourceModel->removeItem(mItem, mDraggedCount);
} }
mSourceModel->update(); mSourceModel->update();
@ -292,8 +292,7 @@ namespace MWGui
if (!onTakeItem(item, item.mCount)) if (!onTakeItem(item, item.mCount))
break; break;
playerModel->copyItem(item, item.mCount); mModel->moveItem(item, item.mCount, playerModel);
mModel->removeItem(item, item.mCount);
} }
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container);
@ -341,7 +340,11 @@ namespace MWGui
} }
else 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; return true;
} }

View file

@ -71,7 +71,7 @@ ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item)
return -1; 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]; const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1];
if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source)) if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source))

View file

@ -21,7 +21,7 @@ namespace MWGui
virtual ModelIndex getIndex (ItemStack item); virtual ModelIndex getIndex (ItemStack item);
virtual size_t getItemCount(); 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 removeItem (const ItemStack& item, size_t count);
virtual void update(); virtual void update();

View file

@ -196,6 +196,16 @@ namespace MWGui
bitmapFile->read(&textureData[0], width*height*4); bitmapFile->read(&textureData[0], width*height*4);
bitmapFile->close(); 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; std::string textureName = name;
Ogre::Image image; Ogre::Image image;
image.loadDynamicImage(&textureData[0], width, height, Ogre::PF_BYTE_RGBA); image.loadDynamicImage(&textureData[0], width, height, Ogre::PF_BYTE_RGBA);
@ -208,18 +218,11 @@ namespace MWGui
// Register the font with MyGUI // Register the font with MyGUI
MyGUI::ResourceManualFont* font = static_cast<MyGUI::ResourceManualFont*>( MyGUI::ResourceManualFont* font = static_cast<MyGUI::ResourceManualFont*>(
MyGUI::FactoryManager::getInstance().createObject("Resource", "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 // 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::Document xmlDocument;
MyGUI::xml::ElementPtr root = xmlDocument.createRoot("ResourceManualFont"); MyGUI::xml::ElementPtr root = xmlDocument.createRoot("ResourceManualFont");
root->addAttribute("name", resourceName);
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
MyGUI::xml::ElementPtr defaultHeight = root->createChild("Property"); MyGUI::xml::ElementPtr defaultHeight = root->createChild("Property");
defaultHeight->addAttribute("key", "DefaultHeight"); defaultHeight->addAttribute("key", "DefaultHeight");
@ -285,6 +288,7 @@ namespace MWGui
font->deserialization(root, MyGUI::Version(3,2,0)); font->deserialization(root, MyGUI::Version(3,2,0));
MyGUI::ResourceManager::getInstance().removeByName(font->getResourceName());
MyGUI::ResourceManager::getInstance().addResource(font); MyGUI::ResourceManager::getInstance().addResource(font);
} }

View file

@ -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); mDrowning->setProgressPosition(progress);
bool isDrowning = (progress == 0); bool isDrowning = (progress == 0);
@ -625,7 +625,7 @@ namespace MWGui
if (mIsDrowning) if (mIsDrowning)
{ {
float intensity = (cos(mDrowningFlashTheta) + 1.0f) / 2.0f; float intensity = (cos(mDrowningFlashTheta) + 1.0f) / 2.0f;
mDrowningFlash->setColour(MyGUI::Colour(intensity, intensity, intensity)); mDrowningFlash->setColour(MyGUI::Colour(intensity, 0, 0));
} }
} }

View file

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

View file

@ -4,6 +4,8 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwmechanics/creaturestats.hpp"
namespace MWGui namespace MWGui
{ {
@ -38,11 +40,11 @@ ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item)
return -1; 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)) if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor))
throw std::runtime_error("Item to copy needs to be from a different container!"); 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"); 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() void InventoryItemModel::update()
{ {
MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(mActor); MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(mActor);

View file

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

View file

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

View file

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

View file

@ -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() ProxyItemModel::~ProxyItemModel()
{ {
delete mSourceModel; 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 // 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) void ProxyItemModel::removeItem (const ItemStack& item, size_t count)

View file

@ -55,7 +55,11 @@ namespace MWGui
virtual void update() = 0; 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; virtual void removeItem (const ItemStack& item, size_t count) = 0;
private: private:
@ -69,7 +73,7 @@ namespace MWGui
{ {
public: public:
virtual ~ProxyItemModel(); 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 void removeItem (const ItemStack& item, size_t count);
virtual ModelIndex getIndex (ItemStack item); virtual ModelIndex getIndex (ItemStack item);

View file

@ -48,7 +48,7 @@ namespace MWGui
void MWList::redraw(bool scrollbarShown) 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 scrollBarWidth = scrollbarShown ? _scrollBarWidth : 0;
const int spacing = 3; const int spacing = 3;
size_t scrollbarPosition = mScrollView->getScrollPosition(); size_t scrollbarPosition = mScrollView->getScrollPosition();
@ -83,7 +83,7 @@ namespace MWGui
else else
{ {
MyGUI::ImageBox* separator = mScrollView->createWidget<MyGUI::ImageBox>("MW_HLine", 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); MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch);
separator->setNeedMouseFocus(false); separator->setNeedMouseFocus(false);
@ -91,7 +91,7 @@ namespace MWGui
} }
++i; ++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) if (!scrollbarShown && mItemHeight > mClient->getSize().height)
redraw(true); redraw(true);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,8 @@
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <components/esm/quickkeys.hpp>
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.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() QuickKeysMenu::~QuickKeysMenu()
{ {
delete mAssignDialog; delete mAssignDialog;
@ -154,8 +164,6 @@ namespace MWGui
frame->setUserString ("ToolTipType", "ItemPtr"); frame->setUserString ("ToolTipType", "ItemPtr");
frame->setUserData(item); frame->setUserData(item);
frame->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); frame->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked);
MyGUI::ImageBox* image = frame->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); MyGUI::ImageBox* image = frame->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default);
std::string path = std::string("icons\\"); std::string path = std::string("icons\\");
path += MWWorld::Class::get(item).getInventoryIcon(item); path += MWWorld::Class::get(item).getInventoryIcon(item);
@ -165,7 +173,8 @@ namespace MWGui
image->setImageTexture (path); image->setImageTexture (path);
image->setNeedMouseFocus (false); image->setNeedMouseFocus (false);
mItemSelectionDialog->setVisible(false); if (mItemSelectionDialog)
mItemSelectionDialog->setVisible(false);
} }
void QuickKeysMenu::onAssignItemCancel() void QuickKeysMenu::onAssignItemCancel()
@ -198,7 +207,8 @@ namespace MWGui
image->setImageTexture (path); image->setImageTexture (path);
image->setNeedMouseFocus (false); image->setNeedMouseFocus (false);
mMagicSelectionDialog->setVisible(false); if (mMagicSelectionDialog)
mMagicSelectionDialog->setVisible(false);
} }
void QuickKeysMenu::onAssignMagic (const std::string& spellId) void QuickKeysMenu::onAssignMagic (const std::string& spellId)
@ -239,7 +249,8 @@ namespace MWGui
image->setImageTexture (path); image->setImageTexture (path);
image->setNeedMouseFocus (false); image->setNeedMouseFocus (false);
mMagicSelectionDialog->setVisible(false); if (mMagicSelectionDialog)
mMagicSelectionDialog->setVisible(false);
} }
void QuickKeysMenu::onAssignMagicCancel () void QuickKeysMenu::onAssignMagicCancel ()
@ -374,6 +385,110 @@ namespace MWGui
center(); 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;
}
}
// --------------------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------------------

View file

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

View file

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

View file

@ -23,6 +23,7 @@ namespace MWGui
: WindowModal("openmw_savegame_dialog.layout") : WindowModal("openmw_savegame_dialog.layout")
, mSaving(true) , mSaving(true)
, mCurrentCharacter(NULL) , mCurrentCharacter(NULL)
, mCurrentSlot(NULL)
{ {
getWidget(mScreenshot, "Screenshot"); getWidget(mScreenshot, "Screenshot");
getWidget(mCharacterSelection, "SelectCharacter"); getWidget(mCharacterSelection, "SelectCharacter");
@ -36,6 +37,7 @@ namespace MWGui
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked);
mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected);
mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected); mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected);
mSaveList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SaveGameDialog::onSlotMouseClick);
mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated); mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated);
mSaveNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onEditSelectAccept); mSaveNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onEditSelectAccept);
mSaveNameEdit->eventEditTextChange += MyGUI::newDelegate(this, &SaveGameDialog::onSaveNameChanged); mSaveNameEdit->eventEditTextChange += MyGUI::newDelegate(this, &SaveGameDialog::onSaveNameChanged);
@ -47,6 +49,37 @@ namespace MWGui
accept(); 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) 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 // 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(); center();
mCharacterSelection->setCaption("");
mCharacterSelection->removeAllItems();
mCurrentCharacter = NULL;
mCurrentSlot = NULL;
mSaveList->removeAllItems();
MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager();
if (mgr->characterBegin() == mgr->characterEnd()) if (mgr->characterBegin() == mgr->characterEnd())
return; return;
@ -78,8 +117,6 @@ namespace MWGui
std::string directory = std::string directory =
Misc::StringUtils::lowerCase (Settings::Manager::getString ("character", "Saves")); Misc::StringUtils::lowerCase (Settings::Manager::getString ("character", "Saves"));
mCharacterSelection->removeAllItems();
int selectedIndex = MyGUI::ITEM_NONE; int selectedIndex = MyGUI::ITEM_NONE;
for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it) for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it)
@ -152,23 +189,10 @@ namespace MWGui
{ {
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); 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 (mSaving)
{ {
// If overwriting an existing slot, ask for confirmation first // If overwriting an existing slot, ask for confirmation first
if (slot != NULL && !reallySure) if (mCurrentSlot != NULL && !reallySure)
{ {
ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog();
dialog->open("#{sMessage4}"); dialog->open("#{sMessage4}");
@ -182,18 +206,22 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage65}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage65}");
return; return;
} }
MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), slot);
}
else
{
if (mCurrentCharacter && slot)
{
MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot);
MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu);
}
} }
setVisible(false); setVisible(false);
MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu);
if (mSaving)
{
MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), mCurrentSlot);
}
else
{
if (mCurrentCharacter && mCurrentSlot)
{
MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot);
}
}
if (MWBase::Environment::get().getStateManager()->getState()== if (MWBase::Environment::get().getStateManager()->getState()==
MWBase::StateManager::State_NoGame) MWBase::StateManager::State_NoGame)
@ -221,6 +249,7 @@ namespace MWGui
assert(character && "Can't find selected character"); assert(character && "Can't find selected character");
mCurrentCharacter = character; mCurrentCharacter = character;
mCurrentSlot = NULL;
fillSaveList(); fillSaveList();
} }
@ -240,6 +269,7 @@ namespace MWGui
{ {
if (pos == MyGUI::ITEM_NONE) if (pos == MyGUI::ITEM_NONE)
{ {
mCurrentSlot = NULL;
mInfoText->setCaption(""); mInfoText->setCaption("");
mScreenshot->setImageTexture(""); mScreenshot->setImageTexture("");
return; return;
@ -248,17 +278,17 @@ namespace MWGui
if (mSaving) if (mSaving)
mSaveNameEdit->setCaption(sender->getItemNameAt(pos)); mSaveNameEdit->setCaption(sender->getItemNameAt(pos));
const MWState::Slot* slot = NULL; mCurrentSlot = NULL;
unsigned int i=0; unsigned int i=0;
for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i) for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i)
{ {
if (i == pos) if (i == pos)
slot = &*it; mCurrentSlot = &*it;
} }
assert(slot && "Can't find selected slot"); assert(mCurrentSlot && "Can't find selected slot");
std::stringstream text; std::stringstream text;
time_t time = slot->mTimeStamp; time_t time = mCurrentSlot->mTimeStamp;
struct tm* timeinfo; struct tm* timeinfo;
timeinfo = localtime(&time); timeinfo = localtime(&time);
@ -269,24 +299,24 @@ namespace MWGui
char buffer[size]; char buffer[size];
if (std::strftime(buffer, size, "%x %X", timeinfo) > 0) if (std::strftime(buffer, size, "%x %X", timeinfo) > 0)
text << buffer << "\n"; text << buffer << "\n";
text << "Level " << slot->mProfile.mPlayerLevel << "\n"; text << "Level " << mCurrentSlot->mProfile.mPlayerLevel << "\n";
text << slot->mProfile.mPlayerCell << "\n"; text << mCurrentSlot->mProfile.mPlayerCell << "\n";
// text << "Time played: " << slot->mProfile.mTimePlayed << "\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; bool pm = hour >= 12;
if (hour >= 13) hour -= 12; if (hour >= 13) hour -= 12;
if (hour == 0) hour = 12; if (hour == 0) hour = 12;
text text
<< slot->mProfile.mInGameTime.mDay << " " << mCurrentSlot->mProfile.mInGameTime.mDay << " "
<< MWBase::Environment::get().getWorld()->getMonthName(slot->mProfile.mInGameTime.mMonth) << MWBase::Environment::get().getWorld()->getMonthName(mCurrentSlot->mProfile.mInGameTime.mMonth)
<< " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); << " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}");
mInfoText->setCaptionWithReplacing(text.str()); mInfoText->setCaptionWithReplacing(text.str());
// Decode screenshot // 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::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
Ogre::Image image; Ogre::Image image;
image.load(stream, "jpg"); image.load(stream, "jpg");

View file

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

View file

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

View file

@ -120,14 +120,11 @@ namespace MWGui
if (i == sourceModel->getItemCount()) if (i == sourceModel->getItemCount())
throw std::runtime_error("The borrowed item disappeared"); 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); 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 // copy the borrowed items to our model
copyItem(item, it->mCount); copyItem(item, it->mCount, setNewOwner);
item.mBase.getCellRef().mOwner = owner;
// then remove them from the source model // then remove them from the source model
sourceModel->removeItem(item, it->mCount); sourceModel->removeItem(item, it->mCount);
} }

View file

@ -78,13 +78,13 @@ namespace MWGui
} }
void TradeWindow::startTrade(const MWWorld::Ptr& actor) void TradeWindow::startTrade(const MWWorld::Ptr& actor)
{ {
mPtr = actor; mPtr = actor;
mCurrentBalance = 0; mCurrentBalance = 0;
mCurrentMerchantOffer = 0; mCurrentMerchantOffer = 0;
checkTradeTime(); checkTradeTime();
std::vector<MWWorld::Ptr> itemSources; std::vector<MWWorld::Ptr> itemSources;
MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources); MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources);
@ -245,7 +245,7 @@ namespace MWGui
// were there any items traded at all? // were there any items traded at all?
std::vector<ItemStack> playerBought = playerItemModel->getItemsBorrowedToUs(); std::vector<ItemStack> playerBought = playerItemModel->getItemsBorrowedToUs();
std::vector<ItemStack> merchantBought = mTradeModel->getItemsBorrowedToUs(); std::vector<ItemStack> merchantBought = mTradeModel->getItemsBorrowedToUs();
if (!playerBought.size() && !merchantBought.size()) if (playerBought.empty() && merchantBought.empty())
{ {
// user notification // user notification
MWBase::Environment::get().getWindowManager()-> MWBase::Environment::get().getWindowManager()->
@ -476,7 +476,7 @@ namespace MWGui
} }
// Relates to NPC gold reset delay // Relates to NPC gold reset delay
void TradeWindow::checkTradeTime() void TradeWindow::checkTradeTime()
{ {
MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(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()); 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); MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr);
MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(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()); 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 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) ) MWBase::Environment::get().getWorld()->getTimeStamp() < sellerStats.getTradeTime() + delay) )
{ {
sellerStats.setTradeTime(MWBase::Environment::get().getWorld()->getTimeStamp()); sellerStats.setTradeTime(MWBase::Environment::get().getWorld()->getTimeStamp());

View file

@ -625,9 +625,9 @@ namespace MWGui
mStatsWindow->setValue (id, value); 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_) void WindowManager::setPlayerClass (const ESM::Class &class_)
@ -1405,16 +1405,49 @@ namespace MWGui
void WindowManager::clear() void WindowManager::clear()
{ {
mMap->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) 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) void WindowManager::playVideo(const std::string &name, bool allowSkipping)

View file

@ -166,8 +166,9 @@ namespace MWGui
virtual void setValue (const std::string& id, int value); virtual void setValue (const std::string& id, int value);
/// Set time left for the player to start drowning (update the drowning bar) /// Set time left for the player to start drowning (update the drowning bar)
/// @param time value from [0,20] /// @param time time left to start drowning
virtual void setDrowningTimeLeft (float time); /// @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 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. 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 /// Clear all savegame-specific data
virtual void clear(); 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 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: private:
bool mConsoleOnlyScripts; bool mConsoleOnlyScripts;

View file

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

View file

@ -29,7 +29,7 @@
#include "aicombat.hpp" #include "aicombat.hpp"
#include "aifollow.hpp" #include "aifollow.hpp"
#include "aipersue.hpp" #include "aipursue.hpp"
namespace namespace
{ {
@ -194,21 +194,11 @@ namespace MWMechanics
+(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1])
+(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2]));
float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified(); 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 ) if( (fight == 100 )
|| (fight >= 95 && d <= 3000) || (fight >= 95 && d <= 3000)
|| (fight >= 90 && d <= 2000) || (fight >= 90 && d <= 2000)
|| (fight >= 80 && d <= 1000) || (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) bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player)
@ -216,7 +206,7 @@ namespace MWMechanics
if (LOS) if (LOS)
{ {
creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr())); creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr()), ptr);
creatureStats.setHostile(true); creatureStats.setHostile(true);
} }
} }
@ -547,7 +537,7 @@ namespace MWMechanics
// TODO: Add AI to follow player and fight for him // TODO: Add AI to follow player and fight for him
AiFollow package(ptr.getRefData().getHandle()); 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 // TODO: VFX_SummonStart, VFX_SummonEnd
creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, creatureStats.mSummonedCreatures.insert(std::make_pair(it->first,
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos).getRefData().getHandle())); 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); CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr);
NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats(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.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.isHostile())
if (ptr.getClass().isClass(ptr, "Guard") && !creatureStats.isHostile())
{ {
/// \todo Move me! I shouldn't be here... /// \todo Move me! I shouldn't be here...
const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
float cutoff = float(esmStore.get<ESM::GameSetting>().find("iCrimeThreshold")->getInt()) * float cutoff = float(esmStore.get<ESM::GameSetting>().find("iCrimeThreshold")->getInt());
float(esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->getInt()) * // Force dialogue on sight if bounty is greater than the cutoff
esmStore.get<ESM::GameSetting>().find("fCrimeGoldDiscountMult")->getFloat(); // In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty)
// Attack on sight if bounty is greater than the cutoff
if ( player.getClass().getNpcStats(player).getBounty() >= cutoff 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().getWorld()->getLOS(ptr, player)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr))
{ {
creatureStats.getAiSequence().stack(AiCombat(player)); creatureStats.getAiSequence().stack(AiPursue(player.getClass().getId(player)), ptr);
creatureStats.setHostile(true); creatureStats.setAlarmed(true);
npcStats.setCrimeId( MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() ); npcStats.setCrimeId(MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId());
} }
} }
// if I was a witness to a crime // if I was a witness to a crime
if (npcStats.getCrimeId() != -1) 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() ) if( npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() )
{ {
// Calm witness down // Calm witness down
if (ptr.getClass().isClass(ptr, "Guard")) if (ptr.getClass().isClass(ptr, "Guard"))
creatureStats.getAiSequence().stopPersue(); creatureStats.getAiSequence().stopPursuit();
creatureStats.getAiSequence().stopCombat(); creatureStats.getAiSequence().stopCombat();
// Reset factors to attack // Reset factors to attack
@ -771,17 +760,16 @@ namespace MWMechanics
else if (!creatureStats.isHostile()) else if (!creatureStats.isHostile())
{ {
if (ptr.getClass().isClass(ptr, "Guard")) if (ptr.getClass().isClass(ptr, "Guard"))
creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player))); creatureStats.getAiSequence().stack(AiPursue(player.getClass().getId(player)), ptr);
else else
creatureStats.getAiSequence().stack(AiCombat(player)); creatureStats.getAiSequence().stack(AiCombat(player), ptr);
creatureStats.setHostile(true); creatureStats.setHostile(true);
} }
} }
// if I didn't report a crime was I attacked? // if I didn't report a crime was I attacked?
else if (creatureStats.getAttacked() && !creatureStats.isHostile()) else if (creatureStats.getAttacked() && !creatureStats.isHostile())
{ {
creatureStats.getAiSequence().stack(AiCombat(player)); creatureStats.getAiSequence().stack(AiCombat(player), ptr);
creatureStats.setHostile(true); creatureStats.setHostile(true);
} }
} }
@ -889,12 +877,23 @@ namespace MWMechanics
iter->second->update(duration); iter->second->update(duration);
} }
// Kill dead actors // Kill dead actors, update some variables
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++)
{ {
const MWWorld::Class &cls = MWWorld::Class::get(iter->first); const MWWorld::Class &cls = MWWorld::Class::get(iter->first);
CreatureStats &stats = cls.getCreatureStats(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(!stats.isDead())
{ {
if(iter->second->isDead()) if(iter->second->isDead())
@ -1028,8 +1027,7 @@ namespace MWMechanics
{ {
const MWWorld::Class &cls = MWWorld::Class::get(iter->first); const MWWorld::Class &cls = MWWorld::Class::get(iter->first);
CreatureStats &stats = cls.getCreatureStats(iter->first); CreatureStats &stats = cls.getCreatureStats(iter->first);
if(!stats.isDead() && stats.getAiSequence().getTypeId() == AiPackage::TypeIdFollow)
if(stats.getAiSequence().getTypeId() == AiPackage::TypeIdFollow)
{ {
MWMechanics::AiFollow* package = static_cast<MWMechanics::AiFollow*>(stats.getAiSequence().getActivePackage()); MWMechanics::AiFollow* package = static_cast<MWMechanics::AiFollow*>(stats.getAiSequence().getActivePackage());
if(package->getFollowedActor() == actor.getCellRef().mRefID) if(package->getFollowedActor() == actor.getCellRef().mRefID)
@ -1050,8 +1048,7 @@ namespace MWMechanics
{ {
const MWWorld::Class &cls = MWWorld::Class::get(*iter); const MWWorld::Class &cls = MWWorld::Class::get(*iter);
CreatureStats &stats = cls.getCreatureStats(*iter); CreatureStats &stats = cls.getCreatureStats(*iter);
if(!stats.isDead() && stats.getAiSequence().getTypeId() == AiPackage::TypeIdCombat)
if(stats.getAiSequence().getTypeId() == AiPackage::TypeIdCombat)
{ {
MWMechanics::AiCombat* package = static_cast<MWMechanics::AiCombat*>(stats.getAiSequence().getActivePackage()); MWMechanics::AiCombat* package = static_cast<MWMechanics::AiCombat*>(stats.getAiSequence().getActivePackage());
if(package->getTargetId() == actor.getCellRef().mRefID) if(package->getTargetId() == actor.getCellRef().mRefID)

View file

@ -31,10 +31,52 @@ namespace
//chooses an attack depending on probability to avoid uniformity //chooses an attack depending on probability to avoid uniformity
void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); 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 namespace MWMechanics
{ {
static const float MAX_ATTACK_DURATION = 0.35f;
static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander
// NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp
@ -45,16 +87,16 @@ namespace MWMechanics
mTimerCombatMove(0), mTimerCombatMove(0),
mFollowTarget(false), mFollowTarget(false),
mReadyToAttack(false), mReadyToAttack(false),
mStrike(false), mAttack(false),
mCombatMove(false), mCombatMove(false),
mBackOffDoor(false),
mRotate(false),
mMovement(), mMovement(),
mForceNoShortcut(false),
mShortcutFailPos(),
mBackOffDoor(false),
mCell(NULL), mCell(NULL),
mDoorIter(actor.getCell()->get<ESM::Door>().mList.end()), mDoorIter(actor.getCell()->get<ESM::Door>().mList.end()),
mDoors(actor.getCell()->get<ESM::Door>()), mDoors(actor.getCell()->get<ESM::Door>()),
mDoorCheckDuration(0), mDoorCheckDuration(0)
mTargetAngle(0)
{ {
} }
@ -125,17 +167,23 @@ namespace MWMechanics
mCombatMove = false; mCombatMove = false;
} }
} }
actor.getClass().getMovementSettings(actor) = mMovement; 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(mTargetAngle))) if(zTurn(actor, Ogre::Degree(mMovement.mRotation[2]))) mMovement.mRotation[2] = 0;
mRotate = false; }
if(mMovement.mRotation[0] != 0)
{
if(smoothTurn(actor, Ogre::Degree(mMovement.mRotation[0]), 0)) mMovement.mRotation[0] = 0;
} }
mTimerAttack -= duration; mTimerAttack -= duration;
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack);
float tReaction = 0.25f; float tReaction = 0.25f;
if(mTimerReact < tReaction) if(mTimerReact < tReaction)
@ -145,7 +193,7 @@ namespace MWMechanics
} }
//Update with period = tReaction //Update with period = tReaction
mTimerReact = 0; mTimerReact = 0;
bool cellChange = mCell && (actor.getCell() != mCell); bool cellChange = mCell && (actor.getCell() != mCell);
@ -156,16 +204,16 @@ namespace MWMechanics
//actual attacking logic //actual attacking logic
//TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f //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(mReadyToAttack)
{ {
if(mTimerAttack <= -attackPeriod) if(mTimerAttack <= -attacksPeriod)
{ {
//TODO: should depend on time between 'start' to 'min attack' //TODO: should depend on time between 'start' to 'min attack'
//for better controlling of NPCs' attack strength. //for better controlling of NPCs' attack strength.
//Also it seems that this time is different for slash/thrust/chop //Also it seems that this time is different for slash/thrust/chop
mTimerAttack = 0.35f * static_cast<float>(rand())/RAND_MAX; mTimerAttack = MAX_ATTACK_DURATION * static_cast<float>(rand())/RAND_MAX;
mStrike = true; mAttack = true;
//say a provoking combat phrase //say a provoking combat phrase
if (actor.getClass().isNpc()) if (actor.getClass().isNpc())
@ -180,30 +228,31 @@ namespace MWMechanics
} }
} }
else if (mTimerAttack <= 0) else if (mTimerAttack <= 0)
mStrike = false; mAttack = false;
} }
else else
{ {
mTimerAttack = -attackPeriod; mTimerAttack = -attacksPeriod;
mStrike = false; mAttack = false;
} }
const MWWorld::Class &cls = actor.getClass(); const MWWorld::Class &actorCls = actor.getClass();
const ESM::Weapon *weapon = NULL; const ESM::Weapon *weapon = NULL;
MWMechanics::WeaponType weaptype; MWMechanics::WeaponType weaptype;
float weapRange, weapSpeed = 1.0f; 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) 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 //Get weapon speed and range
MWWorld::ContainerStoreIterator weaponSlot = 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) if (weaptype == WeapType_HandToHand)
{ {
@ -225,36 +274,27 @@ namespace MWMechanics
weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) 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: * Some notes on meanings of variables:
* *
* rangeMelee: * rangeAttack:
* *
* - Distance where attack using the actor's weapon is possible * - Distance where attack using the actor's weapon is possible:
* - longer for ranged weapons (obviously?) vs. melee weapons * 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 * - 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 * - Applies to melee weapons or hand to hand only (or creatures without
* weapons) * weapons)
* - Distance a little further away from the actor's weapon strike * - Distance a little further away than the actor's weapon reach
* i.e. rangeCloseup > rangeMelee for melee weapons * i.e. rangeFollow > rangeAttack for melee weapons
* (the variable names make this simple concept counter-intuitive, * - Hardcoded value (0 for ranged weapons)
* something like rangeMelee > rangeStrike may be better)
* - Once the target gets beyond this distance mFollowTarget is cleared * - Once the target gets beyond this distance mFollowTarget is cleared
* and a path to the target needs to be found * and a path to the target needs to be found
* - TODO: Possibly rename this variable to rangeMelee or even rangeFollow
* *
* mFollowTarget: * mFollowTarget:
* *
@ -263,58 +303,72 @@ namespace MWMechanics
* available, since the default path without pathgrids is direct to * available, since the default path without pathgrids is direct to
* target even if LOS is not achieved) * target even if LOS is not achieved)
*/ */
float rangeMelee;
float rangeCloseUp; float rangeAttack;
float rangeFollow;
bool distantCombat = false; 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 rangeAttack = 1000; // TODO: should depend on archer skill
rangeCloseUp = 0; //doesn't needed when attacking from distance rangeFollow = 0; // not needed in ranged combat
distantCombat = true; distantCombat = true;
} }
else else
{ {
rangeMelee = weapRange; rangeAttack = weapRange;
rangeCloseUp = 300; 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;
Ogre::Vector3 vStart(pos.pos[0], pos.pos[1], pos.pos[2]); bool isStuck = false;
ESM::Position targetPos = mTarget.getRefData().getPosition(); float speed = 0.0f;
Ogre::Vector3 vDest(targetPos.pos[0], targetPos.pos[1], targetPos.pos[2]); if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = actorCls.getSpeed(actor)) / 10.0f)
Ogre::Vector3 vDir = vDest - vStart; isStuck = true;
float distBetween = vDir.length();
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);
// 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) // (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 //Melee and Close-up combat
vDir.z = 0; mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget);
float dirLen = vDir.length();
mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees();
mRotate = true;
//bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget);
// (not quite strike dist while following) // (not quite strike dist while following)
if (mFollowTarget && distBetween > rangeMelee) if (mFollowTarget && distToTarget > rangeAttack)
{ {
//Close-up combat: just run up on target //Close-up combat: just run up on target
mMovement.mPosition[1] = 1; mMovement.mPosition[1] = 1;
} }
else // (within strike dist) else // (within strike dist)
{ {
//Melee: stop running and attack
mMovement.mPosition[1] = 0; mMovement.mPosition[1] = 0;
// When attacking with a weapon, choose between slash, thrust or chop // set slash/thrust/chop attack
if (actor.getClass().hasInventoryStore(actor)) if (mAttack && !distantCombat) chooseBestAttack(weapon, mMovement);
chooseBestAttack(weapon, mMovement);
if(mMovement.mPosition[0] || mMovement.mPosition[1]) if(mMovement.mPosition[0] || mMovement.mPosition[1])
{ {
mTimerCombatMove = 0.1f + 0.1f * static_cast<float>(rand())/RAND_MAX; mTimerCombatMove = 0.1f + 0.1f * static_cast<float>(rand())/RAND_MAX;
mCombatMove = true; 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 //apply sideway movement (kind of dodging) with some probability
if(static_cast<float>(rand())/RAND_MAX < 0.25) 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; mMovement.mPosition[1] = -1;
} }
@ -335,56 +389,105 @@ namespace MWMechanics
mFollowTarget = true; mFollowTarget = true;
} }
} }
else else // remote pathfinding
{ {
//target is at far distance: build path to target bool preferShortcut = false;
mFollowTarget = false; bool inLOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget);
buildNewPath(actor); //may fail to build a path, check before use if(mReadyToAttack) isStuck = false;
//delete visited path node // check if shortcut is available
mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); if(!isStuck
&& (!mForceNoShortcut
//if no new path leave mTargetAngle unchanged || (Ogre::Vector3(mShortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)
if(!mPathFinder.getPath().empty()) && inLOS)
{ {
//try shortcut if(speed == 0.0f) speed = actorCls.getSpeed(actor);
if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) // maximum dist before pit/obstacle for actor to avoid them depending on his speed
mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); float maxAvoidDist = tReaction * speed + speed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability
else preferShortcut = checkWayIsClear(vActorPos, vTargetPos, distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2);
mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); }
mRotate = true;
// don't use pathgrid when actor can move in 3 dimensions
if(canMoveByZ) preferShortcut = true;
if(preferShortcut)
{
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; mMovement.mPosition[1] = 1;
mReadyToAttack = false; mReadyToAttack = false;
} }
if(distBetween > rangeMelee) if(distToTarget > rangeAttack && !distantCombat)
{ {
//special run attack; it shouldn't affect melee combat tactics //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 //check if actor can overcome the distance = distToTarget - attackerWeapRange
//less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing) //less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing)
//then start attacking //then start attacking
float speed1 = cls.getSpeed(actor); float speed1 = actorCls.getSpeed(actor);
float speed2 = mTarget.getClass().getSpeed(mTarget); float speed2 = mTarget.getClass().getSpeed(mTarget);
if(mTarget.getClass().getMovementSettings(mTarget).mPosition[0] == 0 if(mTarget.getClass().getMovementSettings(mTarget).mPosition[0] == 0
&& mTarget.getClass().getMovementSettings(mTarget).mPosition[1] == 0) && mTarget.getClass().getMovementSettings(mTarget).mPosition[1] == 0)
speed2 = 0; speed2 = 0;
float s1 = distBetween - weapRange; float s1 = distToTarget - weapRange;
float t = s1/speed1; float t = s1/speed1;
float s2 = speed2 * t; 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) if (t + s2/speed1 <= t_swing)
{ {
mReadyToAttack = true; mReadyToAttack = true;
if(mTimerAttack <= -attackPeriod) if(mTimerAttack <= -attacksPeriod)
{ {
mTimerAttack = 0.3f*static_cast<float>(rand())/RAND_MAX; mTimerAttack = MAX_ATTACK_DURATION * static_cast<float>(rand())/RAND_MAX;
mStrike = true; mAttack = true;
} }
} }
} }
@ -394,7 +497,7 @@ namespace MWMechanics
// coded at 250ms or 1/4 second // coded at 250ms or 1/4 second
// //
// TODO: Add a parameter to vary DURATION_SAME_SPOT? // 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 mObstacleCheck.check(actor, tReaction)) // check if evasive action needed
{ {
// first check if we're walking into a door // first check if we're walking into a door
@ -406,12 +509,11 @@ namespace MWMechanics
// Check all the doors in this cell // Check all the doors in this cell
mDoors = cell->get<ESM::Door>(); // update mDoors = cell->get<ESM::Door>(); // update
mDoorIter = mDoors.mList.begin(); mDoorIter = mDoors.mList.begin();
Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos);
for (; mDoorIter != mDoors.mList.end(); ++mDoorIter) for (; mDoorIter != mDoors.mList.end(); ++mDoorIter)
{ {
MWWorld::LiveCellRef<ESM::Door>& ref = *mDoorIter; MWWorld::LiveCellRef<ESM::Door>& ref = *mDoorIter;
float minSqr = 1.3*1.3*MIN_DIST_TO_DOOR_SQUARED; // for legibility 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 ref.mData.getLocalRotation().rot[2] < 0.4f) // even small opening
{ {
//std::cout<<"closed door id \""<<ref.mRef.mRefID<<"\""<<std::endl; //std::cout<<"closed door id \""<<ref.mRef.mRefID<<"\""<<std::endl;
@ -441,11 +543,10 @@ namespace MWMechanics
} }
MWWorld::LiveCellRef<ESM::Door>& ref = *mDoorIter; 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 float minSqr = 1.6 * 1.6 * MIN_DIST_TO_DOOR_SQUARED; // for legibility
// TODO: add reaction to checking open doors // TODO: add reaction to checking open doors
if(mBackOffDoor && 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 mMovement.mPosition[1] = -0.2; // back off, but slowly
} }
@ -454,47 +555,38 @@ namespace MWMechanics
ref.mData.getLocalRotation().rot[2] >= 1) ref.mData.getLocalRotation().rot[2] >= 1)
{ {
mDoorIter = mDoors.mList.end(); mDoorIter = mDoors.mList.end();
mBackOffDoor = false; mBackOffDoor = false;
//std::cout<<"open door id \""<<ref.mRef.mRefID<<"\""<<std::endl; //std::cout<<"open door id \""<<ref.mRef.mRefID<<"\""<<std::endl;
mMovement.mPosition[1] = 1; mMovement.mPosition[1] = 1;
} }
else // these lines break ranged combat distance keeping
{ //else
mMovement.mPosition[1] = 1; // FIXME: oscillation? //{
} // mMovement.mPosition[1] = 1; // FIXME: oscillation?
//}
actor.getClass().getMovementSettings(actor) = mMovement;
return false; return false;
} }
void AiCombat::buildNewPath(const MWWorld::Ptr& actor) void AiCombat::buildNewPath(const MWWorld::Ptr& actor)
{ {
//Construct path to target Ogre::Vector3 newPathTarget = Ogre::Vector3(mTarget.getRefData().getPosition().pos);
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 float dist;
if(!mPathFinder.getPath().empty()) if(!mPathFinder.getPath().empty())
{ {
ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back();
Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); 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; float targetPosThreshold = (actor.getCell()->getCell()->isExterior())? 300 : 100;
bool isOutside = actor.getCell()->getCell()->isExterior();
if (isOutside)
targetPosThreshold = 300;
else
targetPosThreshold = 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::Position pos = actor.getRefData().getPosition();
ESM::Pathgrid::Point start; ESM::Pathgrid::Point start;
@ -502,17 +594,18 @@ namespace MWMechanics
start.mY = pos.pos[1]; start.mY = pos.pos[1];
start.mZ = pos.pos[2]; start.mZ = pos.pos[2];
ESM::Pathgrid::Point dest;
dest.mX = newPathTarget.x;
dest.mY = newPathTarget.y;
dest.mZ = newPathTarget.z;
if(!mPathFinder.isPathConstructed()) if(!mPathFinder.isPathConstructed())
mPathFinder.buildPath(start, dest, actor.getCell(), isOutside); mPathFinder.buildPath(start, dest, actor.getCell(), false);
else else
{ {
PathFinder newPathFinder; 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()) if(!mPathFinder.getPath().empty())
{ {
newPathFinder.syncStart(mPathFinder.getPath()); newPathFinder.syncStart(mPathFinder.getPath());

View file

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

View file

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

View file

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

View file

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

View file

@ -38,7 +38,7 @@ MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& s
copy (sequence); copy (sequence);
mDone = sequence.mDone; mDone = sequence.mDone;
} }
return *this; return *this;
} }
@ -51,7 +51,7 @@ int MWMechanics::AiSequence::getTypeId() const
{ {
if (mPackages.empty()) if (mPackages.empty())
return -1; return -1;
return mPackages.front()->getTypeId(); 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(); delete *mPackages.begin();
mPackages.erase (mPackages.begin()); mPackages.erase (mPackages.begin());
@ -93,16 +93,21 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration)
{ {
if (!mPackages.empty()) if (!mPackages.empty())
{ {
mLastAiPackage = mPackages.front()->getTypeId(); MWMechanics::AiPackage* package = mPackages.front();
if (mPackages.front()->execute (actor,duration)) mLastAiPackage = package->getTypeId();
if (package->execute (actor,duration))
{ {
delete *mPackages.begin(); // To account for the rare case where AiPackage::execute() queued another AI package
mPackages.erase (mPackages.begin()); // (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; mDone = true;
} }
else else
{ {
mDone = false; mDone = false;
} }
} }
} }
@ -116,9 +121,19 @@ void MWMechanics::AiSequence::clear()
mPackages.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()) if(mPackages.front()->getPriority() <= package.getPriority())
{ {

View file

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

View file

@ -1,6 +1,7 @@
#include "aiwander.hpp" #include "aiwander.hpp"
#include <OgreVector3.h> #include <OgreVector3.h>
#include <OgreSceneNode.h>
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -37,6 +38,8 @@ namespace MWMechanics
, mRotate(false) , mRotate(false)
, mTargetAngle(0) , mTargetAngle(0)
, mSaidGreeting(false) , mSaidGreeting(false)
, mHasReturnPosition(false)
, mReturnPosition(0,0,0)
{ {
for(unsigned short counter = 0; counter < mIdle.size(); counter++) 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 // Reduce the turning animation glitch by using a *HUGE* value of
// epsilon... TODO: a proper fix might be in either the physics or the // epsilon... TODO: a proper fix might be in either the physics or the
// animation subsystem // animation subsystem
if (zTurn(actor, Ogre::Degree(mTargetAngle), Ogre::Degree(12))) if (zTurn(actor, Ogre::Degree(mTargetAngle), Ogre::Degree(5)))
mRotate = false; mRotate = false;
} }
@ -330,21 +333,44 @@ namespace MWMechanics
if(mDistance && cellChange) if(mDistance && cellChange)
mDistance = 0; mDistance = 0;
// 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)
{
mChooseAction = false;
mIdleNow = false;
if (!mPathFinder.isPathConstructed())
{
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())
{
mMoveNow = false;
mWalking = true;
}
}
}
if(mChooseAction) if(mChooseAction)
{ {
mPlayedIdle = 0; mPlayedIdle = 0;
unsigned short idleRoll = 0; getRandomIdle(); // NOTE: sets mPlayedIdle with a random selection
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;
}
}
if(!mPlayedIdle && mDistance) if(!mPlayedIdle && mDistance)
{ {
@ -375,7 +401,7 @@ namespace MWMechanics
} }
// Allow interrupting a walking actor to trigger a greeting // 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 // Play a random voice greeting if the player gets too close
int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified();
@ -395,6 +421,8 @@ namespace MWMechanics
mMoveNow = false; mMoveNow = false;
mWalking = false; mWalking = false;
mObstacleCheck.clear(); mObstacleCheck.clear();
mIdleNow = true;
getRandomIdle();
} }
if(!mRotate) if(!mRotate)
@ -402,11 +430,11 @@ namespace MWMechanics
Ogre::Vector3 dir = playerPos - actorPos; Ogre::Vector3 dir = playerPos - actorPos;
float length = dir.length(); float length = dir.length();
// FIXME: horrible hack
float faceAngle = Ogre::Radian(Ogre::Math::ACos(dir.y / length) * float faceAngle = Ogre::Radian(Ogre::Math::ACos(dir.y / length) *
((Ogre::Math::ASin(dir.x / length).valueRadians()>0)?1.0:-1.0)).valueDegrees(); ((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 // 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; mTargetAngle = faceAngle;
mRotate = true; mRotate = true;
@ -432,7 +460,8 @@ namespace MWMechanics
} }
// Check if idle animation finished // Check if idle animation finished
if(!checkIdle(actor, mPlayedIdle)) // FIXME: don't stay forever
if(!checkIdle(actor, mPlayedIdle) && playerDistSqr > helloDistance*helloDistance)
{ {
mPlayedIdle = 0; mPlayedIdle = 0;
mIdleNow = false; mIdleNow = false;
@ -503,6 +532,7 @@ namespace MWMechanics
mMoveNow = false; mMoveNow = false;
mWalking = false; mWalking = false;
mChooseAction = true; mChooseAction = true;
mHasReturnPosition = false;
} }
return false; // AiWander package not yet completed return false; // AiWander package not yet completed
@ -586,5 +616,30 @@ namespace MWMechanics
else else
return false; 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;
}
}
}
} }

View file

@ -2,8 +2,11 @@
#define GAME_MWMECHANICS_AIWANDER_H #define GAME_MWMECHANICS_AIWANDER_H
#include "aipackage.hpp" #include "aipackage.hpp"
#include <vector> #include <vector>
#include <OgreVector3.h>
#include "pathfinding.hpp" #include "pathfinding.hpp"
#include "obstacle.hpp" #include "obstacle.hpp"
@ -22,10 +25,15 @@ namespace MWMechanics
virtual int getTypeId() const; virtual int getTypeId() const;
///< 0: Wander ///< 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: private:
void stopWalking(const MWWorld::Ptr& actor); void stopWalking(const MWWorld::Ptr& actor);
void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
bool checkIdle(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 mDistance; // how far the actor can wander from the spawn point
int mDuration; int mDuration;
@ -38,6 +46,10 @@ namespace MWMechanics
float mGreetDistanceReset; float mGreetDistanceReset;
float mChance; 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 // Cached current cell location
int mCellX; int mCellX;
int mCellY; int mCellY;

View file

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

View file

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

View file

@ -41,6 +41,8 @@ namespace MWMechanics
bool mHostile; bool mHostile;
bool mAttackingOrSpell; bool mAttackingOrSpell;
bool mKnockdown; bool mKnockdown;
bool mKnockdownOneFrame;
bool mKnockdownOverOneFrame;
bool mHitRecovery; bool mHitRecovery;
bool mBlock; bool mBlock;
unsigned int mMovementFlags; unsigned int mMovementFlags;
@ -188,7 +190,14 @@ namespace MWMechanics
float getEvasion() const; float getEvasion() const;
void setKnockedDown(bool value); void setKnockedDown(bool value);
///Returns true for the entire duration of the actor being knocked down
bool getKnockedDown() const; 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); void setHitRecovery(bool value);
bool getHitRecovery() const; bool getHitRecovery() const;
void setBlock(bool value); void setBlock(bool value);

View file

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

View file

@ -325,22 +325,22 @@ namespace MWMechanics
} }
// used by AiCombat, see header for the rationale // 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) 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>::const_iterator oldStart = path.begin();
std::list<ESM::Pathgrid::Point>::iterator iter = ++mPath.begin(); std::list<ESM::Pathgrid::Point>::iterator iter = ++mPath.begin();
if( (*iter).mX == oldStart->mX if( (*iter).mX == oldStart->mX
&& (*iter).mY == oldStart->mY && (*iter).mY == oldStart->mY
&& (*iter).mZ == oldStart->mZ && (*iter).mZ == oldStart->mZ)
&& (*iter).mAutogenerated == oldStart->mAutogenerated
&& (*iter).mConnectionNum == oldStart->mConnectionNum )
{ {
mPath.pop_front(); mPath.pop_front();
return true;
} }
return false;
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -10,9 +10,9 @@
namespace MWMechanics 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); Ogre::Radian diff (targetAngle - currentAngle);
if (diff >= Ogre::Degree(180)) if (diff >= Ogre::Degree(180))
{ {
@ -30,13 +30,17 @@ bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree eps
if (absDiff < epsilon) if (absDiff < epsilon)
return true; return true;
// Max. speed of 10 radian per sec Ogre::Radian limit = MAX_VEL_ANGULAR * MWBase::Environment::get().getFrameDuration();
Ogre::Radian limit = Ogre::Radian(10) * MWBase::Environment::get().getFrameDuration();
if (absDiff > limit) if (absDiff > limit)
diff = Ogre::Math::Sign(diff) * 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; return false;
} }
bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree epsilon)
{
return smoothTurn(actor, targetAngle, 2, epsilon);
}
} }

View file

@ -10,11 +10,17 @@ class Ptr;
namespace MWMechanics 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) /// configure rotation settings for an actor to reach this target angle (eventually)
/// @return have we reached the target angle? /// @return have we reached the target angle?
bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle,
Ogre::Degree epsilon = Ogre::Degree(0.5)); 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 #endif

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -97,7 +97,7 @@ namespace MWScript
return mScripts.size(); 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()); for (std::map<std::string, std::pair<bool, Locals> >::const_iterator iter (mScripts.begin());
iter!=mScripts.end(); ++iter) iter!=mScripts.end(); ++iter)
@ -113,6 +113,7 @@ namespace MWScript
writer.startRecord (ESM::REC_GSCR); writer.startRecord (ESM::REC_GSCR);
script.save (writer); script.save (writer);
writer.endRecord (ESM::REC_GSCR); writer.endRecord (ESM::REC_GSCR);
progress.increaseProgress();
} }
} }

View file

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

View file

@ -302,15 +302,23 @@ namespace MWScript
std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first; 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> 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 MWWorld::ESMStore &store = world->getStore();
const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId); 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 "";
return faction->mRanks[it->second]; return faction->mRanks[rank];
} }
std::string InterpreterContext::getPCNextRank() const std::string InterpreterContext::getPCNextRank() const
@ -320,25 +328,25 @@ namespace MWScript
std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first; 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.find(factionId);
int rank = -1;
if (it != ranks.end())
rank = it->second;
++rank; // Next rank
// if we are already at max rank, there is no next rank
if (rank > 9)
rank = 9;
const MWWorld::ESMStore &store = world->getStore(); const MWWorld::ESMStore &store = world->getStore();
const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId); const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId);
std::map<std::string, int> ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks(); if(rank < 0 || rank > 9)
return "";
if (!ranks.empty()) return faction->mRanks[rank];
{
std::map<std::string, int>::const_iterator it = ranks.begin();
if(it->second < -1 || it->second > 9)
return "";
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];
} }
int InterpreterContext::getPCBounty() const int InterpreterContext::getPCBounty() const
@ -370,10 +378,14 @@ namespace MWScript
float InterpreterContext::getDistance (const std::string& name, const std::string& id) const 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, false);
const MWWorld::Ptr ref2 = getReference (id, 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]; double diff[3];

View file

@ -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> template <class R>
class OpIsWerewolf : public Interpreter::Opcode0 class OpIsWerewolf : public Interpreter::Opcode0
{ {
@ -1236,6 +1252,8 @@ namespace MWScript
interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath<ImplicitRef>); interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeOnDeathExplicit, new OpOnDeath<ExplicitRef>); 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::opcodeIsWerewolf, new OpIsWerewolf<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolfExplicit, new OpIsWerewolf<ExplicitRef>); 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::opcodeUndoWerewolf, new OpSetWerewolf<ImplicitRef, false>);
interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolfExplicit, new OpSetWerewolf<ExplicitRef, false>); interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolfExplicit, new OpSetWerewolf<ExplicitRef, false>);
interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobatics, new OpSetWerewolfAcrobatics<ImplicitRef>); interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobatics, new OpSetWerewolfAcrobatics<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics<ExplicitRef>); interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics<ExplicitRef>);
} }
} }
} }

View file

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

View file

@ -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) const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profile)
{ {
addSlot (profile); addSlot (profile);
@ -102,6 +117,21 @@ const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profi
return &mSlots.back(); 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) const MWState::Slot *MWState::Character::updateSlot (const Slot *slot, const ESM::SavedGame& profile)
{ {
int index = slot - &mSlots[0]; int index = slot - &mSlots[0];
@ -150,4 +180,4 @@ ESM::SavedGame MWState::Character::getSignature() const
slot = *iter; slot = *iter;
return slot.mProfile; return slot.mProfile;
} }

View file

@ -36,11 +36,19 @@ namespace MWState
Character (const boost::filesystem::path& saves, const std::string& game); 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); const Slot *createSlot (const ESM::SavedGame& profile);
///< Create new slot. ///< Create new slot.
/// ///
/// \attention The ownership of the slot is not transferred. /// \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); const Slot *updateSlot (const Slot *slot, const ESM::SavedGame& profile);
/// \note Slot must belong to this character. /// \note Slot must belong to this character.
/// ///

View file

@ -47,6 +47,25 @@ MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create)
return mCurrent; 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() void MWState::CharacterManager::createCharacter()
{ {
std::ostringstream stream; std::ostringstream stream;

View file

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

View file

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

View file

@ -44,6 +44,9 @@ namespace MWState
virtual void endGame(); 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); 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. ///< Write a saved game to \a slot or create a new slot if \a slot == 0.
/// ///

View file

@ -3,7 +3,6 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/player.hpp" #include "../mwworld/player.hpp"
@ -24,7 +23,12 @@ namespace MWWorld
void ActionRead::executeImp (const MWWorld::Ptr& actor) { 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}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage4}");
return; return;
} }

View file

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

View file

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

View file

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

View file

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

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