diff --git a/CMakeLists.txt b/CMakeLists.txt index 9de8efeb5..ee3850ddb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 30) +set(OPENMW_VERSION_MINOR 31) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") @@ -325,12 +325,14 @@ else () add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="_d") endif() -add_definitions(-DOGRE_PLUGIN_DIR_REL="${OGRE_PLUGIN_DIR_REL}") -add_definitions(-DOGRE_PLUGIN_DIR_DBG="${OGRE_PLUGIN_DIR_DBG}") if (APPLE AND OPENMW_OSX_DEPLOYMENT) # make it empty so plugin loading code can check this and try to find plugins inside app bundle add_definitions(-DOGRE_PLUGIN_DIR="") else() + if (NOT DEFINED ${OGRE_PLUGIN_DIR}) + set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_REL}) + endif() + add_definitions(-DOGRE_PLUGIN_DIR="${OGRE_PLUGIN_DIR}") endif() diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index eef96c8c9..ea908590a 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -249,6 +249,9 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) std::cout << " Refnum: " << ref.mRefNum.mIndex << std::endl; std::cout << " ID: '" << ref.mRefID << "'\n"; std::cout << " Owner: '" << ref.mOwner << "'\n"; + std::cout << " Global: '" << ref.mGlobalVariable << "'" << std::endl; + std::cout << " Faction: '" << ref.mFaction << "'" << std::endl; + std::cout << " Faction rank: '" << ref.mFactionRank << "'" << std::endl; std::cout << " Enchantment charge: '" << ref.mEnchantmentCharge << "'\n"; std::cout << " Uses/health: '" << ref.mCharge << "'\n"; std::cout << " Gold value: '" << ref.mGoldValue << "'\n"; diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index e4ada7d18..f8bc2af61 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -513,7 +513,7 @@ void Record::print() else std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl; std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; - std::cout << " NAM0: " << mData.mNAM0 << std::endl; + std::cout << " RefId counter: " << mData.mRefIdCounter << std::endl; } diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 08c105c92..6a116151c 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -61,8 +61,13 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator - cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool - scenetoolmode infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable + cellcreator referenceablecreator referencecreator scenesubview + infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable + ) + +opencs_units_noqt (view/world + subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate + scripthighlighter idvalidator dialoguecreator ) opencs_units (view/render @@ -75,12 +80,10 @@ opencs_units_noqt (view/render lightingbright object cell ) -opencs_units_noqt (view/world - subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate - scripthighlighter idvalidator dialoguecreator +opencs_units (view/widget + scenetoolbar scenetool scenetoolmode pushbutton ) - opencs_units (view/tools reportsubview ) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 0c033593c..3fad05f65 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -977,13 +977,13 @@ namespace CSMWorld virtual QVariant get (const Record& record) const { - return record.get().mFactIndex; + return record.get().mFactionRank; } virtual void set (Record& record, const QVariant& data) { ESXRecordT record2 = record.get(); - record2.mFactIndex = data.toInt(); + record2.mFactionRank = data.toInt(); record.setModified (record2); } diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 5d59492c6..799a07e14 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -81,6 +81,7 @@ QWidget *CSVDoc::StartupDialogue::createTools() config->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); config->setIcon (QIcon (":startup/configure")); + config->setToolTip ("Open user settings"); layout->addWidget (config); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 76f6db385..ebd3eb764 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -11,7 +12,7 @@ #include #include -#include "../world/scenetoolmode.hpp" +#include "../widget/scenetoolmode.hpp" #include "navigation.hpp" #include "lighting.hpp" @@ -51,16 +52,34 @@ namespace CSVRender QTimer *timer = new QTimer (this); connect (timer, SIGNAL (timeout()), this, SLOT (update())); - timer->start (20); /// \todo make this configurable + timer->start (20); ///< \todo make this configurable + + /// \todo make shortcut configurable + QShortcut *focusToolbar = new QShortcut (Qt::Key_T, this, 0, 0, Qt::WidgetWithChildrenShortcut); + connect (focusToolbar, SIGNAL (activated()), this, SIGNAL (focusToolbarRequest())); } - CSVWorld::SceneToolMode *SceneWidget::makeLightingSelector (CSVWorld::SceneToolbar *parent) + CSVWidget::SceneToolMode *SceneWidget::makeLightingSelector (CSVWidget::SceneToolbar *parent) { - CSVWorld::SceneToolMode *tool = new CSVWorld::SceneToolMode (parent); + CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Lighting Mode"); - tool->addButton (":door.png", "day"); /// \todo replace icons - tool->addButton (":GMST.png", "night"); - tool->addButton (":Info.png", "bright"); + /// \todo replace icons + tool->addButton (":door.png", "day", + "Day" + "
  • Cell specific ambient in interiors
  • " + "
  • Low ambient in exteriors
  • " + "
  • Strong directional light source/lir>" + "
  • This mode closely resembles day time in-game
"); + tool->addButton (":GMST.png", "night", + "Night" + "
  • Cell specific ambient in interiors
  • " + "
  • Low ambient in exteriors
  • " + "
  • Weak directional light source
  • " + "
  • This mode closely resembles night time in-game
"); + tool->addButton (":Info.png", "bright", + "Bright" + "
  • Maximum ambient
  • " + "
  • Strong directional light source
"); connect (tool, SIGNAL (modeChanged (const std::string&)), this, SLOT (selectLightingMode (const std::string&))); @@ -347,6 +366,9 @@ namespace CSVRender mLighting = lighting; mLighting->activate (mSceneMgr, mHasDefaultAmbient ? &mDefaultAmbient : 0); + + if (mWindow) + mWindow->update(); } void SceneWidget::selectLightingMode (const std::string& mode) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index f6b41942f..8f548f483 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -16,7 +16,7 @@ namespace Ogre class RenderWindow; } -namespace CSVWorld +namespace CSVWidget { class SceneToolMode; class SceneToolbar; @@ -38,7 +38,7 @@ namespace CSVRender QPaintEngine* paintEngine() const; - CSVWorld::SceneToolMode *makeLightingSelector (CSVWorld::SceneToolbar *parent); + CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); ///< \attention The created tool is not added to the toolbar (via addTool). Doing that /// is the responsibility of the calling function. @@ -111,6 +111,10 @@ namespace CSVRender void update(); void selectLightingMode (const std::string& mode); + + signals: + + void focusToolbarRequest(); }; } diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index fee2f0a16..d3413a29d 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -7,8 +7,9 @@ #include -#include "../world/scenetoolmode.hpp" -#include +#include "../../model/world/universalid.hpp" + +#include "../widget/scenetoolmode.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) : SceneWidget (parent), mDocument(document) @@ -53,14 +54,39 @@ void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() setNavigation (&m1st); } -CSVWorld::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( - CSVWorld::SceneToolbar *parent) +CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( + CSVWidget::SceneToolbar *parent) { - CSVWorld::SceneToolMode *tool = new CSVWorld::SceneToolMode (parent); + CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Camera Mode"); - tool->addButton (":door.png", "1st"); /// \todo replace icons - tool->addButton (":GMST.png", "free"); - tool->addButton (":Info.png", "orbit"); + /// \todo replace icons + /// \todo consider user-defined button-mapping + tool->addButton (":door.png", "1st", + "First Person" + "
  • Mouse-Look while holding the left button
  • " + "
  • WASD movement keys
  • " + "
  • Mouse wheel moves the camera forawrd/backward
  • " + "
  • Stafing (also vertically) by holding the left mouse button and control
  • " + "
  • Camera is held upright
  • " + "
  • Hold shift to speed up movement
  • " + "
"); + tool->addButton (":GMST.png", "free", + "Free Camera" + "
  • Mouse-Look while holding the left button
  • " + "
  • Stafing (also vertically) via WASD or by holding the left mouse button and control
  • " + "
  • Mouse wheel moves the camera forawrd/backward
  • " + "
  • Roll camera with Q and E keys
  • " + "
  • Hold shift to speed up movement
  • " + "
"); + tool->addButton (":Info.png", "orbit", + "Orbiting Camera" + "
  • Always facing the centre point
  • " + "
  • Rotate around the centre point via WASD or by moving the mouse while holding the left button
  • " + "
  • Mouse wheel moves camera away or towards centre point but can not pass through it
  • " + "
  • Roll camera with Q and E keys
  • " + "
  • Stafing (also vertically) by holding the left mouse button and control (includes relocation of the centre point)
  • " + "
  • Hold shift to speed up movement
  • " + "
"); connect (tool, SIGNAL (modeChanged (const std::string&)), this, SLOT (selectNavigationMode (const std::string&))); diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 2af90b0fe..3b96779a8 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -13,7 +13,7 @@ namespace CSMWorld { class UniversalId; } -namespace CSVWorld +namespace CSVWidget { class SceneToolMode; class SceneToolbar; @@ -49,7 +49,7 @@ namespace CSVRender WorldspaceWidget (CSMDoc::Document& document, QWidget *parent = 0); - CSVWorld::SceneToolMode *makeNavigationSelector (CSVWorld::SceneToolbar *parent); + CSVWidget::SceneToolMode *makeNavigationSelector (CSVWidget::SceneToolbar *parent); ///< \attention The created tool is not added to the toolbar (via addTool). Doing that /// is the responsibility of the calling function. diff --git a/apps/opencs/view/widget/pushbutton.cpp b/apps/opencs/view/widget/pushbutton.cpp new file mode 100644 index 000000000..056e548fb --- /dev/null +++ b/apps/opencs/view/widget/pushbutton.cpp @@ -0,0 +1,83 @@ + +#include "pushbutton.hpp" + +#include +#include + +void CSVWidget::PushButton::setExtendedToolTip (const QString& text) +{ + QString tooltip = text; + + if (tooltip.isEmpty()) + tooltip = "(Tool tip not implemented yet)"; + + switch (mType) + { + case Type_TopMode: + + tooltip += + "

(left click to change mode)"; + + break; + + case Type_Mode: + + tooltip += + "

(left click to activate," + "
shift-left click to activate and keep panel open)"; + + break; + } + + setToolTip (tooltip); +} + +void CSVWidget::PushButton::keyPressEvent (QKeyEvent *event) +{ + if (event->key()!=Qt::Key_Shift) + mKeepOpen = false; + + QPushButton::keyPressEvent (event); +} + +void CSVWidget::PushButton::keyReleaseEvent (QKeyEvent *event) +{ + if (event->key()==Qt::Key_Return || event->key()==Qt::Key_Enter) + { + mKeepOpen = event->modifiers() & Qt::ShiftModifier; + emit clicked(); + } + + QPushButton::keyReleaseEvent (event); +} + +void CSVWidget::PushButton::mouseReleaseEvent (QMouseEvent *event) +{ + mKeepOpen = event->button()==Qt::LeftButton && (event->modifiers() & Qt::ShiftModifier); + QPushButton::mouseReleaseEvent (event); +} + +CSVWidget::PushButton::PushButton (const QIcon& icon, Type type, const QString& tooltip, + QWidget *parent) +: QPushButton (icon, "", parent), mKeepOpen (false), mType (type), mToolTip (tooltip) +{ + setCheckable (type==Type_Mode); + setExtendedToolTip (tooltip); +} + +CSVWidget::PushButton::PushButton (Type type, const QString& tooltip, QWidget *parent) +: QPushButton (parent), mKeepOpen (false), mType (type), mToolTip (tooltip) +{ + setCheckable (type==Type_Mode); + setExtendedToolTip (tooltip); +} + +bool CSVWidget::PushButton::hasKeepOpen() const +{ + return mKeepOpen; +} + +QString CSVWidget::PushButton::getBaseToolTip() const +{ + return mToolTip; +} \ No newline at end of file diff --git a/apps/opencs/view/widget/pushbutton.hpp b/apps/opencs/view/widget/pushbutton.hpp new file mode 100644 index 000000000..9b90bd0c6 --- /dev/null +++ b/apps/opencs/view/widget/pushbutton.hpp @@ -0,0 +1,55 @@ +#ifndef CSV_WIDGET_PUSHBUTTON_H +#define CSV_WIDGET_PUSHBUTTON_H + +#include + +namespace CSVWidget +{ + class PushButton : public QPushButton + { + Q_OBJECT + + public: + + enum Type + { + Type_TopMode, // top level button for mode selector panel + Type_Mode // mode button + }; + + private: + + bool mKeepOpen; + Type mType; + QString mToolTip; + + private: + + void setExtendedToolTip (const QString& text); + + protected: + + virtual void keyPressEvent (QKeyEvent *event); + + virtual void keyReleaseEvent (QKeyEvent *event); + + virtual void mouseReleaseEvent (QMouseEvent *event); + + public: + + /// \param push Do not maintain a toggle state + PushButton (const QIcon& icon, Type type, const QString& tooltip = "", + QWidget *parent = 0); + + /// \param push Do not maintain a toggle state + PushButton (Type type, const QString& tooltip = "", + QWidget *parent = 0); + + bool hasKeepOpen() const; + + /// Return tooltip used at construction (without any button-specific modifications) + QString getBaseToolTip() const; + }; +} + +#endif diff --git a/apps/opencs/view/world/scenetool.cpp b/apps/opencs/view/widget/scenetool.cpp similarity index 73% rename from apps/opencs/view/world/scenetool.cpp rename to apps/opencs/view/widget/scenetool.cpp index 612b4c6d3..e3f2dfd1c 100644 --- a/apps/opencs/view/world/scenetool.cpp +++ b/apps/opencs/view/widget/scenetool.cpp @@ -3,7 +3,8 @@ #include "scenetoolbar.hpp" -CSVWorld::SceneTool::SceneTool (SceneToolbar *parent) : QPushButton (parent) +CSVWidget::SceneTool::SceneTool (SceneToolbar *parent) +: PushButton (PushButton::Type_TopMode, "", parent) { setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); setIconSize (QSize (parent->getIconSize(), parent->getIconSize())); @@ -12,7 +13,7 @@ CSVWorld::SceneTool::SceneTool (SceneToolbar *parent) : QPushButton (parent) connect (this, SIGNAL (clicked()), this, SLOT (openRequest())); } -void CSVWorld::SceneTool::openRequest() +void CSVWidget::SceneTool::openRequest() { showPanel (parentWidget()->mapToGlobal (pos())); } diff --git a/apps/opencs/view/world/scenetool.hpp b/apps/opencs/view/widget/scenetool.hpp similarity index 66% rename from apps/opencs/view/world/scenetool.hpp rename to apps/opencs/view/widget/scenetool.hpp index 07e8b58d7..24099683e 100644 --- a/apps/opencs/view/world/scenetool.hpp +++ b/apps/opencs/view/widget/scenetool.hpp @@ -1,14 +1,14 @@ -#ifndef CSV_WORLD_SCENETOOL_H -#define CSV_WORLD_SCENETOOL_H +#ifndef CSV_WIDGET_SCENETOOL_H +#define CSV_WIDGET_SCENETOOL_H -#include +#include "pushbutton.hpp" -namespace CSVWorld +namespace CSVWidget { class SceneToolbar; ///< \brief Tool base class - class SceneTool : public QPushButton + class SceneTool : public PushButton { Q_OBJECT diff --git a/apps/opencs/view/widget/scenetoolbar.cpp b/apps/opencs/view/widget/scenetoolbar.cpp new file mode 100644 index 000000000..eac9bcec3 --- /dev/null +++ b/apps/opencs/view/widget/scenetoolbar.cpp @@ -0,0 +1,47 @@ + +#include "scenetoolbar.hpp" + +#include +#include + +#include "scenetool.hpp" + +void CSVWidget::SceneToolbar::focusInEvent (QFocusEvent *event) +{ + QWidget::focusInEvent (event); + + if (mLayout->count()) + dynamic_cast (*mLayout->itemAt (0)).widget()->setFocus(); +} + +CSVWidget::SceneToolbar::SceneToolbar (int buttonSize, QWidget *parent) +: QWidget (parent), mButtonSize (buttonSize), mIconSize (buttonSize-6) +{ + setFixedWidth (mButtonSize); + + mLayout = new QVBoxLayout (this); + mLayout->setAlignment (Qt::AlignTop); + + mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); + + setLayout (mLayout); + + /// \todo make shortcut configurable + QShortcut *focusScene = new QShortcut (Qt::Key_T, this, 0, 0, Qt::WidgetWithChildrenShortcut); + connect (focusScene, SIGNAL (activated()), this, SIGNAL (focusSceneRequest())); +} + +void CSVWidget::SceneToolbar::addTool (SceneTool *tool) +{ + mLayout->addWidget (tool, 0, Qt::AlignTop); +} + +int CSVWidget::SceneToolbar::getButtonSize() const +{ + return mButtonSize; +} + +int CSVWidget::SceneToolbar::getIconSize() const +{ + return mIconSize; +} diff --git a/apps/opencs/view/world/scenetoolbar.hpp b/apps/opencs/view/widget/scenetoolbar.hpp similarity index 66% rename from apps/opencs/view/world/scenetoolbar.hpp rename to apps/opencs/view/widget/scenetoolbar.hpp index 731806cc5..1902ba2be 100644 --- a/apps/opencs/view/world/scenetoolbar.hpp +++ b/apps/opencs/view/widget/scenetoolbar.hpp @@ -1,11 +1,11 @@ -#ifndef CSV_WORLD_SCENETOOLBAR_H -#define CSV_WORLD_SCENETOOLBAR_H +#ifndef CSV_WIDGET_SCENETOOLBAR_H +#define CSV_WIDGET_SCENETOOLBAR_H #include class QVBoxLayout; -namespace CSVWorld +namespace CSVWidget { class SceneTool; @@ -17,6 +17,10 @@ namespace CSVWorld int mButtonSize; int mIconSize; + protected: + + virtual void focusInEvent (QFocusEvent *event); + public: SceneToolbar (int buttonSize, QWidget *parent = 0); @@ -26,6 +30,10 @@ namespace CSVWorld int getButtonSize() const; int getIconSize() const; + + signals: + + void focusSceneRequest(); }; } diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp new file mode 100644 index 000000000..caedfa3ee --- /dev/null +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -0,0 +1,86 @@ + +#include "scenetoolmode.hpp" + +#include +#include +#include + +#include "scenetoolbar.hpp" +#include "pushbutton.hpp" + +void CSVWidget::SceneToolMode::adjustToolTip (const PushButton *activeMode) +{ + QString toolTip = mToolTip; + + toolTip += "

Currently selected: " + activeMode->getBaseToolTip(); + + toolTip += "

(left click to change mode)"; + + setToolTip (toolTip); +} + +CSVWidget::SceneToolMode::SceneToolMode (SceneToolbar *parent, const QString& toolTip) +: SceneTool (parent), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()), + mToolTip (toolTip), mFirst (0) +{ + mPanel = new QFrame (this, Qt::Popup); + + mLayout = new QHBoxLayout (mPanel); + + mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); + + mPanel->setLayout (mLayout); +} + +void CSVWidget::SceneToolMode::showPanel (const QPoint& position) +{ + mPanel->move (position); + mPanel->show(); + + if (mFirst) + mFirst->setFocus (Qt::OtherFocusReason); +} + +void CSVWidget::SceneToolMode::addButton (const std::string& icon, const std::string& id, + const QString& tooltip) +{ + PushButton *button = new PushButton (QIcon (QPixmap (icon.c_str())), PushButton::Type_Mode, + tooltip, mPanel); + button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); + button->setIconSize (QSize (mIconSize, mIconSize)); + button->setFixedSize (mButtonSize, mButtonSize); + + mLayout->addWidget (button); + + mButtons.insert (std::make_pair (button, id)); + + connect (button, SIGNAL (clicked()), this, SLOT (selected())); + + if (mButtons.size()==1) + { + mFirst = button; + setIcon (button->icon()); + button->setChecked (true); + adjustToolTip (button); + } +} + +void CSVWidget::SceneToolMode::selected() +{ + std::map::const_iterator iter = + mButtons.find (dynamic_cast (sender())); + + if (iter!=mButtons.end()) + { + if (!iter->first->hasKeepOpen()) + mPanel->hide(); + + for (std::map::const_iterator iter2 = mButtons.begin(); + iter2!=mButtons.end(); ++iter2) + iter2->first->setChecked (iter2==iter); + + setIcon (iter->first->icon()); + adjustToolTip (iter->first); + emit modeChanged (iter->second); + } +} \ No newline at end of file diff --git a/apps/opencs/view/world/scenetoolmode.hpp b/apps/opencs/view/widget/scenetoolmode.hpp similarity index 57% rename from apps/opencs/view/world/scenetoolmode.hpp rename to apps/opencs/view/widget/scenetoolmode.hpp index a156c0c95..9959f9835 100644 --- a/apps/opencs/view/world/scenetoolmode.hpp +++ b/apps/opencs/view/widget/scenetoolmode.hpp @@ -1,5 +1,5 @@ -#ifndef CSV_WORLD_SCENETOOL_MODE_H -#define CSV_WORLD_SCENETOOL_MODE_H +#ifndef CSV_WIDGET_SCENETOOL_MODE_H +#define CSV_WIDGET_SCENETOOL_MODE_H #include "scenetool.hpp" @@ -7,9 +7,10 @@ class QHBoxLayout; -namespace CSVWorld +namespace CSVWidget { class SceneToolbar; + class PushButton; ///< \brief Mode selector tool class SceneToolMode : public SceneTool @@ -18,17 +19,22 @@ namespace CSVWorld QWidget *mPanel; QHBoxLayout *mLayout; - std::map mButtons; // widget, id + std::map mButtons; // widget, id int mButtonSize; int mIconSize; + QString mToolTip; + PushButton *mFirst; + + void adjustToolTip (const PushButton *activeMode); public: - SceneToolMode (SceneToolbar *parent); + SceneToolMode (SceneToolbar *parent, const QString& toolTip); virtual void showPanel (const QPoint& position); - void addButton (const std::string& icon, const std::string& id); + void addButton (const std::string& icon, const std::string& id, + const QString& tooltip = ""); signals: diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 673d3573f..c3be0f11b 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -471,25 +471,32 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM QHBoxLayout *buttonsLayout = new QHBoxLayout; QToolButton* prevButton = new QToolButton(mainWidget); prevButton->setIcon(QIcon(":/go-previous.png")); + prevButton->setToolTip ("Switch to previous record"); QToolButton* nextButton = new QToolButton(mainWidget); nextButton->setIcon(QIcon(":/go-next.png")); + nextButton->setToolTip ("Switch to next record"); buttonsLayout->addWidget(prevButton, 0); buttonsLayout->addWidget(nextButton, 1); buttonsLayout->addStretch(2); QToolButton* cloneButton = new QToolButton(mainWidget); cloneButton->setIcon(QIcon(":/edit-clone.png")); + cloneButton->setToolTip ("Clone record"); QToolButton* addButton = new QToolButton(mainWidget); addButton->setIcon(QIcon(":/add.png")); + addButton->setToolTip ("Add new record"); QToolButton* deleteButton = new QToolButton(mainWidget); deleteButton->setIcon(QIcon(":/edit-delete.png")); + deleteButton->setToolTip ("Delete record"); QToolButton* revertButton = new QToolButton(mainWidget); revertButton->setIcon(QIcon(":/edit-undo.png")); + revertButton->setToolTip ("Revert record"); if (mTable->getFeatures() & CSMWorld::IdTable::Feature_Preview) { QToolButton* previewButton = new QToolButton(mainWidget); previewButton->setIcon(QIcon(":/edit-preview.png")); + previewButton->setToolTip ("Open a preview of this record"); buttonsLayout->addWidget(previewButton); connect(previewButton, SIGNAL(clicked()), this, SLOT(showPreview())); } @@ -498,6 +505,7 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM { QToolButton* viewButton = new QToolButton(mainWidget); viewButton->setIcon(QIcon(":/cell.png")); + viewButton->setToolTip ("Open a scene view of the cell this record is located in"); buttonsLayout->addWidget(viewButton); connect(viewButton, SIGNAL(clicked()), this, SLOT(viewRecord())); } diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index 49e6d9361..1e106c69f 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -5,8 +5,8 @@ #include "../render/previewwidget.hpp" -#include "scenetoolbar.hpp" -#include "scenetoolmode.hpp" +#include "../widget/scenetoolbar.hpp" +#include "../widget/scenetoolmode.hpp" CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id) @@ -28,9 +28,9 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo else mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), true, this); - SceneToolbar *toolbar = new SceneToolbar (48+6, this); + CSVWidget::SceneToolbar *toolbar = new CSVWidget::SceneToolbar (48+6, this); - SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); + CSVWidget::SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); toolbar->addTool (lightingTool); layout->addWidget (toolbar, 0); @@ -46,6 +46,8 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo connect (mScene, SIGNAL (closeRequest()), this, SLOT (closeRequest())); connect (mScene, SIGNAL (referenceableIdChanged (const std::string&)), this, SLOT (referenceableIdChanged (const std::string&))); + connect (mScene, SIGNAL (focusToolbarRequest()), toolbar, SLOT (setFocus())); + connect (toolbar, SIGNAL (focusSceneRequest()), mScene, SLOT (setFocus())); } void CSVWorld::PreviewSubView::setEditLock (bool locked) {} diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 36cce9ecd..dc1525b05 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -17,9 +17,11 @@ #include "../render/pagedworldspacewidget.hpp" #include "../render/unpagedworldspacewidget.hpp" +#include "../widget/scenetoolbar.hpp" +#include "../widget/scenetoolmode.hpp" + #include "tablebottombox.hpp" #include "creator.hpp" -#include "scenetoolmode.hpp" CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mLayout(new QHBoxLayout), mDocument(document), mScene(NULL), mToolbar(NULL) @@ -95,18 +97,18 @@ void CSVWorld::SceneSubView::makeConnections (CSVRender::PagedWorldspaceWidget* this, SLOT (cellSelectionChanged (const CSMWorld::CellSelection&))); } -CSVWorld::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::WorldspaceWidget* widget, widgetType type) +CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::WorldspaceWidget* widget, widgetType type) { - CSVWorld::SceneToolbar* toolbar = new SceneToolbar (48+6, this); + CSVWidget::SceneToolbar* toolbar = new CSVWidget::SceneToolbar (48+6, this); - SceneToolMode *navigationTool = widget->makeNavigationSelector (toolbar); + CSVWidget::SceneToolMode *navigationTool = widget->makeNavigationSelector (toolbar); toolbar->addTool (navigationTool); - SceneToolMode *lightingTool = widget->makeLightingSelector (toolbar); + CSVWidget::SceneToolMode *lightingTool = widget->makeLightingSelector (toolbar); toolbar->addTool (lightingTool); /* Add buttons specific to the type. For now no need for it. - * + * switch (type) { case widget_Paged: @@ -188,7 +190,7 @@ void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalI { CSVRender::PagedWorldspaceWidget* pagedNewWidget = NULL; CSVRender::UnpagedWorldspaceWidget* unPagedNewWidget = NULL; - SceneToolbar* toolbar = NULL; + CSVWidget::SceneToolbar* toolbar = NULL; switch (mScene->getDropRequirements(CSVRender::WorldspaceWidget::getDropType(data))) { @@ -217,7 +219,7 @@ void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalI } } -void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceWidget* widget, CSVWorld::SceneToolbar* toolbar) +void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceWidget* widget, CSVWidget::SceneToolbar* toolbar) { assert(mLayout); @@ -236,8 +238,12 @@ void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceW mScene = widget; mToolbar = toolbar; + connect (mScene, SIGNAL (focusToolbarRequest()), mToolbar, SLOT (setFocus())); + connect (mToolbar, SIGNAL (focusSceneRequest()), mScene, SLOT (setFocus())); + mLayout->addWidget (mToolbar, 0); mLayout->addWidget (mScene, 1); mScene->selectDefaultNavigationMode(); + setFocusProxy (mScene); } \ No newline at end of file diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index b9ecbe931..b9dcdd6a3 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -4,7 +4,6 @@ #include #include "../doc/subview.hpp" -#include "scenetoolbar.hpp" class QModelIndex; @@ -25,6 +24,11 @@ namespace CSVRender class UnpagedWorldspaceWidget; } +namespace CSVWidget +{ + class SceneToolbar; +} + namespace CSVWorld { class Table; @@ -39,7 +43,7 @@ namespace CSVWorld CSVRender::WorldspaceWidget *mScene; QHBoxLayout* mLayout; CSMDoc::Document& mDocument; - SceneToolbar* mToolbar; + CSVWidget::SceneToolbar* mToolbar; public: @@ -59,14 +63,15 @@ namespace CSVWorld void makeConnections(CSVRender::UnpagedWorldspaceWidget* widget); - void replaceToolbarAndWorldspace(CSVRender::WorldspaceWidget* widget, SceneToolbar* toolbar); + void replaceToolbarAndWorldspace(CSVRender::WorldspaceWidget* widget, CSVWidget::SceneToolbar* toolbar); enum widgetType { widget_Paged, widget_Unpaged }; - SceneToolbar* makeToolbar(CSVRender::WorldspaceWidget* widget, widgetType type); + + CSVWidget::SceneToolbar* makeToolbar(CSVRender::WorldspaceWidget* widget, widgetType type); private slots: diff --git a/apps/opencs/view/world/scenetoolbar.cpp b/apps/opencs/view/world/scenetoolbar.cpp deleted file mode 100644 index d60240da7..000000000 --- a/apps/opencs/view/world/scenetoolbar.cpp +++ /dev/null @@ -1,34 +0,0 @@ - -#include "scenetoolbar.hpp" - -#include - -#include "scenetool.hpp" - -CSVWorld::SceneToolbar::SceneToolbar (int buttonSize, QWidget *parent) -: QWidget (parent), mButtonSize (buttonSize), mIconSize (buttonSize-6) -{ - setFixedWidth (mButtonSize); - - mLayout = new QVBoxLayout (this); - mLayout->setAlignment (Qt::AlignTop); - - mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); - - setLayout (mLayout); -} - -void CSVWorld::SceneToolbar::addTool (SceneTool *tool) -{ - mLayout->addWidget (tool, 0, Qt::AlignTop); -} - -int CSVWorld::SceneToolbar::getButtonSize() const -{ - return mButtonSize; -} - -int CSVWorld::SceneToolbar::getIconSize() const -{ - return mIconSize; -} \ No newline at end of file diff --git a/apps/opencs/view/world/scenetoolmode.cpp b/apps/opencs/view/world/scenetoolmode.cpp deleted file mode 100644 index 73b01ae3a..000000000 --- a/apps/opencs/view/world/scenetoolmode.cpp +++ /dev/null @@ -1,57 +0,0 @@ - -#include "scenetoolmode.hpp" - -#include -#include -#include - -#include "scenetoolbar.hpp" - -CSVWorld::SceneToolMode::SceneToolMode (SceneToolbar *parent) -: SceneTool (parent), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()) -{ - mPanel = new QFrame (this, Qt::Popup); - - mLayout = new QHBoxLayout (mPanel); - - mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); - - mPanel->setLayout (mLayout); -} - -void CSVWorld::SceneToolMode::showPanel (const QPoint& position) -{ - mPanel->move (position); - mPanel->show(); -} - -void CSVWorld::SceneToolMode::addButton (const std::string& icon, const std::string& id) -{ - QPushButton *button = new QPushButton (QIcon (QPixmap (icon.c_str())), "", mPanel); - button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); - button->setIconSize (QSize (mIconSize, mIconSize)); - button->setFixedSize (mButtonSize, mButtonSize); - - mLayout->addWidget (button); - - mButtons.insert (std::make_pair (button, id)); - - connect (button, SIGNAL (clicked()), this, SLOT (selected())); - - if (mButtons.size()==1) - setIcon (button->icon()); -} - -void CSVWorld::SceneToolMode::selected() -{ - std::map::const_iterator iter = - mButtons.find (dynamic_cast (sender())); - - if (iter!=mButtons.end()) - { - mPanel->hide(); - - setIcon (iter->first->icon()); - emit modeChanged (iter->second); - } -} \ No newline at end of file diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 5c5b0d16c..0dda1131b 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -69,7 +69,7 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting - disease pickpocket levelledlist combat steering obstacle + disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index f2b71bd4c..f718972b9 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -196,6 +196,10 @@ namespace MWBase /// @param bias Can be used to add an additional aggression bias towards the target, /// making it more likely for the function to return true. virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0, bool ignoreDistance=false) = 0; + + /// Usually done once a frame, but can be invoked manually in time-critical situations. + /// This will increase the death count for any actors that were killed. + virtual void killDeadActors() = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 49ac5bc15..29f326a1b 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -398,8 +398,9 @@ namespace MWBase /// open or close a non-teleport door (depending on current state) virtual void activateDoor(const MWWorld::Ptr& door) = 0; - /// open or close a non-teleport door as specified - virtual void activateDoor(const MWWorld::Ptr& door, bool open) = 0; + /// update movement state of a non-teleport door as specified + /// @param state see MWClass::setDoorState + virtual void activateDoor(const MWWorld::Ptr& door, int state) = 0; virtual bool getPlayerStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is standing on \a object diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 043aadd35..5bd144a73 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -97,8 +97,7 @@ namespace MWClass std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index d61ba038a..a162c3edd 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -127,8 +127,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index b29bf36b2..e102e7cf1 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -247,8 +247,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 0adee57e3..824b69daf 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -139,8 +139,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index dc98e323e..bbe5f60bf 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -193,8 +193,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 53add4274..362b97902 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -235,8 +235,7 @@ namespace MWClass text += "\n#{sTrapped}"; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index feb6a2714..a74ee6fd6 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -9,6 +9,7 @@ #include "../mwmechanics/movement.hpp" #include "../mwmechanics/disease.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/difficultyscaling.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -254,6 +255,23 @@ namespace MWClass if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false); + + // Weapon health is reduced by 1 even if the attack misses + const bool weaphashealth = !weapon.isEmpty() && weapon.getClass().hasItemHealth(weapon); + if(weaphashealth) + { + int weaphealth = weapon.getClass().getItemHealth(weapon); + + if (!MWBase::Environment::get().getWorld()->getGodModeState()) + { + weaphealth -= std::min(1, weaphealth); + weapon.getCellRef().setCharge(weaphealth); + } + + // Weapon broken? unequip it + if (weapon.getCellRef().getCharge() == 0) + weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr); + } return; } @@ -290,13 +308,13 @@ namespace MWClass attack = weapon.get()->mBase->mData.mThrust; if(attack) { - float weaponDamage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); - weaponDamage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f); + damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); + damage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f); if(weaphashealth) { int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); int weaphealth = weapon.getClass().getItemHealth(weapon); - weaponDamage *= float(weaphealth) / weapmaxhealth; + damage *= float(weaphealth) / weapmaxhealth; if (!MWBase::Environment::get().getWorld()->getGodModeState()) { @@ -311,8 +329,6 @@ namespace MWClass if (weapon.getCellRef().getCharge() == 0) weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr); } - - damage += weaponDamage; } // Apply "On hit" enchanted weapons @@ -330,6 +346,8 @@ namespace MWClass } } + MWMechanics::applyElementalShields(ptr, victim); + if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) damage = 0; @@ -376,6 +394,9 @@ namespace MWClass if (damage > 0.0f && !object.isEmpty()) MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); + if (damage < 0.001f) + damage = 0; + if (damage > 0.f) { // Check for knockdown @@ -391,8 +412,13 @@ namespace MWClass else getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? + damage = std::max(1.f, damage); + if(ishealth) { + if (!attacker.isEmpty()) + damage = scaleDamage(damage, attacker, ptr); + MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); float health = getCreatureStats(ptr).getHealth().getCurrent() - damage; setActorHealth(ptr, health, attacker); @@ -618,8 +644,8 @@ namespace MWClass float Creature::getArmorRating (const MWWorld::Ptr& ptr) const { - /// \todo add Shield magic effect magnitude here, controlled by a GMST (Vanilla vs MCP behaviour) - return 0.f; + // Note this is currently unused. Creatures do not use armor mitigation. + return getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Shield).mMagnitude; } float Creature::getCapacity (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index e435511b9..d0ba37f0b 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -62,7 +62,7 @@ namespace MWClass const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); if (customData.mDoorState > 0) { - MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState == 1 ? true : false); + MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState); } } } @@ -250,8 +250,10 @@ namespace MWClass text += "\n#{sTrapped}"; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); - + } info.text = text; return info; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 683092923..fa03f23ff 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -147,8 +147,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 8a2c20f69..8237b07f2 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -187,8 +187,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index bc6855129..b0129f403 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -141,8 +141,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 1044fb01d..74b10ee3d 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -182,8 +182,7 @@ namespace MWClass } if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c003e0b22..49e016d4f 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -22,6 +22,8 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/disease.hpp" #include "../mwmechanics/combat.hpp" +#include "../mwmechanics/autocalcspell.hpp" +#include "../mwmechanics/difficultyscaling.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" @@ -53,6 +55,24 @@ namespace return new NpcCustomData (*this); } + int is_even(double d) { + double int_part; + modf(d / 2.0, &int_part); + return 2.0 * int_part == d; + } + + int round_ieee_754(double d) { + double i = floor(d); + d -= i; + if(d < 0.5) + return i; + if(d > 0.5) + return i + 1.0; + if(is_even(i)) + return i; + return i + 1.0; + } + void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) { // race bonus @@ -108,8 +128,9 @@ namespace } modifierSum += add; } - creatureStats.setAttribute(attribute, std::min(creatureStats.getAttribute(attribute).getBase() - + static_cast((level-1) * modifierSum+0.5), 100) ); + creatureStats.setAttribute(attribute, std::min( + round_ieee_754(creatureStats.getAttribute(attribute).getBase() + + (level-1) * modifierSum), 100) ); } // initial health @@ -193,18 +214,6 @@ namespace majorMultiplier = 1.0f; break; } - if (class_->mData.mSkills[k][1] == skillIndex) - { - // Major skill -> add starting spells for this skill if existing - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - MWWorld::Store::iterator it = store.get().begin(); - for (; it != store.get().end(); ++it) - { - if (it->mData.mFlags & ESM::Spell::F_Autocalc - && MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(&*it, ptr)) == skillIndex) - npcStats.getSpells().add(it->mId); - } - } } // is this skill in the same Specialization as the class? @@ -217,12 +226,25 @@ namespace npcStats.getSkill(skillIndex).setBase( std::min( - npcStats.getSkill(skillIndex).getBase() + round_ieee_754( + npcStats.getSkill(skillIndex).getBase() + 5 + raceBonus + specBonus - + static_cast((level-1) * (majorMultiplier + specMultiplier)), 100)); + +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 } + + int skills[ESM::Skill::Length]; + for (int i=0; i spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); + for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) + npcStats.getSpells().add(*it); } } @@ -258,6 +280,7 @@ namespace MWClass gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); gmst.fDamageStrengthBase = store.find("fDamageStrengthBase"); gmst.fDamageStrengthMult = store.find("fDamageStrengthMult"); + gmst.fCombatArmorMinMult = store.find("fCombatArmorMinMult"); inited = true; } @@ -506,6 +529,24 @@ namespace MWClass if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { othercls.onHit(victim, 0.0f, false, weapon, ptr, false); + + // Weapon health is reduced by 1 even if the attack misses + const bool weaphashealth = !weapon.isEmpty() && weapon.getClass().hasItemHealth(weapon); + if(weaphashealth) + { + int weaphealth = weapon.getClass().getItemHealth(weapon); + + if (!MWBase::Environment::get().getWorld()->getGodModeState()) + { + weaphealth -= std::min(1, weaphealth); + weapon.getCellRef().setCharge(weaphealth); + } + + // Weapon broken? unequip it + if (weaphealth == 0) + weapon = *inv.unequipItem(weapon, ptr); + } + return; } @@ -560,8 +601,8 @@ namespace MWClass damage = stats.getSkill(weapskill).getModified(); damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength()); - healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f) - || (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0); + healthdmg = (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0) + || otherstats.getKnockedDown(); if(stats.isWerewolf()) { healthdmg = true; @@ -583,15 +624,21 @@ namespace MWClass sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f); } if(ptr.getRefData().getHandle() == "player") + { skillUsageSucceeded(ptr, weapskill, 0); - bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); - if(!detected) - { - damage *= store.find("fCombatCriticalStrikeMult")->getFloat(); - MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); - MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence(); + + bool unaware = !seq.isInCombat() + && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); + if(unaware) + { + damage *= store.find("fCombatCriticalStrikeMult")->getFloat(); + MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); + MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + } } + if (othercls.getCreatureStats(victim).getKnockedDown()) damage *= store.find("fCombatKODamageMult")->getFloat(); @@ -609,6 +656,8 @@ namespace MWClass } } + MWMechanics::applyElementalShields(ptr, victim); + if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) damage = 0; @@ -631,6 +680,14 @@ namespace MWClass !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker)) MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); + if (!attacker.isEmpty() && attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr) + && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker)) + { + // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. + // Note: accidental or collateral damage attacks are ignored. + MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); + } + bool wasDead = getCreatureStats(ptr).isDead(); getCreatureStats(ptr).setAttacked(true); @@ -658,6 +715,9 @@ namespace MWClass if (damage > 0.0f && !object.isEmpty()) MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); + if (damage < 0.001f) + damage = 0; + if(damage > 0.0f) { // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying @@ -686,7 +746,7 @@ namespace MWClass else getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? - if(ishealth && !attacker.isEmpty()) // Don't use armor mitigation for fall damage + if(damage > 0 && ishealth && !attacker.isEmpty()) // Don't use armor mitigation for fall damage { // Hit percentages: // cuirass = 30% @@ -706,9 +766,12 @@ namespace MWClass }; int hitslot = hitslots[(int)(::rand()/(RAND_MAX+1.0)*20.0)]; - float damagediff = damage; - damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f); - damagediff -= damage; + float unmitigatedDamage = damage; + float x = damage / (damage + getArmorRating(ptr)); + damage *= std::max(gmst.fCombatArmorMinMult->getFloat(), x); + int damageDiff = unmitigatedDamage - damage; + if (damage < 1) + damage = 1; MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot); @@ -716,13 +779,13 @@ namespace MWClass if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name()) { int armorhealth = armor.getClass().getItemHealth(armor); - armorhealth -= std::min(std::max(1, (int)damagediff), + armorhealth -= std::min(std::max(1, damageDiff), armorhealth); armor.getCellRef().setCharge(armorhealth); // Armor broken? unequip it if (armorhealth == 0) - inv.unequipItem(armor, ptr); + armor = *inv.unequipItem(armor, ptr); if (ptr.getRefData().getHandle() == "player") skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0); @@ -747,6 +810,9 @@ namespace MWClass if(ishealth) { + if (!attacker.isEmpty()) + damage = scaleDamage(damage, attacker, ptr); + if(damage > 0.0f) sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); float health = getCreatureStats(ptr).getHealth().getCurrent() - damage; @@ -827,6 +893,7 @@ namespace MWClass if(ptr.getRefData().getHandle() == "player") return boost::shared_ptr(new MWWorld::ActionTalk(actor)); + // Werewolfs can't activate NPCs if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -837,6 +904,7 @@ namespace MWClass return action; } + if(getCreatureStats(ptr).isDead()) return boost::shared_ptr(new MWWorld::ActionOpen(ptr, true)); if(ptr.getClass().getCreatureStats(ptr).isHostile()) @@ -844,8 +912,10 @@ namespace MWClass if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)) return boost::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing + // Can't talk to werewolfs + if(ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).isWerewolf()) + return boost::shared_ptr (new MWWorld::FailedAction("")); return boost::shared_ptr(new MWWorld::ActionTalk(ptr)); - } MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index d6b1f8c26..b0ca0a658 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -40,6 +40,7 @@ namespace MWClass const ESM::GameSetting *iKnockDownOddsBase; const ESM::GameSetting *fDamageStrengthBase; const ESM::GameSetting *fDamageStrengthMult; + const ESM::GameSetting *fCombatArmorMinMult; }; static const GMST& getGmst(); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 440121d35..734385a6a 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -151,8 +151,7 @@ namespace MWClass info.isPotion = true; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index ed8625eec..5df7da2b0 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -140,8 +140,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index d7a080534..ed09a06da 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -144,8 +144,7 @@ namespace MWClass text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 66affa599..6f75095ff 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -351,8 +351,7 @@ namespace MWClass info.remainingEnchantCharge = ptr.getCellRef().getEnchantmentCharge(); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); - text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 8d9dc670f..6c801f755 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -266,7 +266,7 @@ namespace MWDialogue } catch (const std::exception& error) { - std::cerr << std::string ("Dialogue error: An exception has been thrown: ") + error.what(); + std::cerr << std::string ("Dialogue error: An exception has been thrown: ") + error.what() << std::endl; } } } @@ -505,9 +505,10 @@ namespace MWDialogue void DialogueManager::askQuestion (const std::string& question, int choice) { + mIsInChoice = true; + MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); win->addChoice(question, choice); - mIsInChoice = true; } void DialogueManager::goodbye() diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 08cdb1d00..daa9a684a 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -76,8 +76,18 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const } else if (info.mData.mRank != -1) { - // if there is a rank condition, but the NPC is not in a faction, always fail - return false; + if (isCreature) + return false; + + // Rank requirement, but no faction given. Use the actor's faction, if there is one. + MWMechanics::NpcStats& stats = mActor.getClass().getNpcStats (mActor); + + if (!stats.getFactionRanks().size()) + return false; + + // check rank + if (stats.getFactionRanks().begin()->second < info.mData.mRank) + return false; } // Gender diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index d0ac3e7c3..6ac8c9d18 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -153,6 +153,13 @@ void CompanionWindow::onReferenceUnavailable() MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); } +void CompanionWindow::resetReference() +{ + ReferenceInterface::resetReference(); + mItemView->setModel(NULL); + mModel = NULL; + mSortModel = NULL; +} } diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index 006d0a3c3..dc460e2fc 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -20,6 +20,8 @@ namespace MWGui virtual void exit(); + virtual void resetReference(); + void open(const MWWorld::Ptr& npc); void onFrame (); diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 011feb4d3..f0212031e 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -195,15 +195,6 @@ namespace MWGui { if (mPtr.getTypeName() == typeid(ESM::Container).name()) { - // check that we don't exceed container capacity - MWWorld::Ptr item = mDragAndDrop->mItem.mBase; - float weight = item.getClass().getWeight(item) * mDragAndDrop->mDraggedCount; - if (mPtr.getClass().getCapacity(mPtr) < mPtr.getClass().getEncumbrance(mPtr) + weight) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); - return; - } - // check container organic flag MWWorld::LiveCellRef* ref = mPtr.get(); if (ref->mBase->mFlags & ESM::Container::Organic) @@ -212,6 +203,15 @@ namespace MWGui messageBox("#{sContentsMessage2}"); return; } + + // check that we don't exceed container capacity + MWWorld::Ptr item = mDragAndDrop->mItem.mBase; + float weight = item.getClass().getWeight(item) * mDragAndDrop->mDraggedCount; + if (mPtr.getClass().getCapacity(mPtr) < mPtr.getClass().getEncumbrance(mPtr) + weight) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); + return; + } } mDragAndDrop->drop(mModel, mItemView); @@ -258,6 +258,14 @@ namespace MWGui onTakeAllButtonClicked(mTakeButton); } + void ContainerWindow::resetReference() + { + ReferenceInterface::resetReference(); + mItemView->setModel(NULL); + mModel = NULL; + mSortModel = NULL; + } + void ContainerWindow::close() { WindowBase::close(); diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 5446a4ab7..79951f70e 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -54,6 +54,8 @@ namespace MWGui void open(const MWWorld::Ptr& container, bool loot=false); virtual void close(); + virtual void resetReference(); + virtual void exit(); private: diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index a6ab1f122..2283e0cbe 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -273,8 +273,13 @@ namespace MWGui { if ((!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice()) && !mGoodbye) - return; - MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + { + // in choice, not allowed to escape, but give access to main menu to allow loading other saves + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + MWBase::Environment::get().getSoundManager()->pauseSounds (MWBase::SoundManager::Play_TypeSfx); + } + else + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); } void DialogueWindow::onWindowResize(MyGUI::Window* _sender) diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp index b7c2007b4..92d9a25b6 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/apps/openmw/mwgui/fontloader.cpp @@ -260,21 +260,28 @@ namespace MWGui // More hacks! The french game uses several win1252 characters that are not included // in the cp437 encoding of the font. Fall back to similar available characters. - // Same for U+2013 - std::map additional; - additional[39] = 0x2019; // apostrophe - additional[45] = 0x2013; // dash - if (additional.find(i) != additional.end() && mEncoding == ToUTF8::CP437) + if (mEncoding == ToUTF8::CP437) { - MyGUI::xml::ElementPtr code = codes->createChild("Code"); - code->addAttribute("index", additional[i]); - code->addAttribute("coord", MyGUI::utility::toString(x1) + " " - + MyGUI::utility::toString(y1) + " " - + MyGUI::utility::toString(w) + " " - + MyGUI::utility::toString(h)); - code->addAttribute("advance", data[i].width); - code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " - + MyGUI::utility::toString((fontSize-data[i].ascent))); + std::multimap additional; + additional.insert(std::make_pair(39, 0x2019)); // apostrophe + additional.insert(std::make_pair(45, 0x2013)); // dash + additional.insert(std::make_pair(34, 0x201D)); // right double quotation mark + additional.insert(std::make_pair(34, 0x201C)); // left double quotation mark + for (std::multimap::iterator it = additional.begin(); it != additional.end(); ++it) + { + if (it->first != i) + continue; + + MyGUI::xml::ElementPtr code = codes->createChild("Code"); + code->addAttribute("index", it->second); + code->addAttribute("coord", MyGUI::utility::toString(x1) + " " + + MyGUI::utility::toString(y1) + " " + + MyGUI::utility::toString(w) + " " + + MyGUI::utility::toString(h)); + code->addAttribute("advance", data[i].width); + code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + + MyGUI::utility::toString((fontSize-data[i].ascent))); + } } // ASCII vertical bar, use this as text input cursor diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index b39fd0db7..3e753b7f9 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -92,6 +92,7 @@ namespace MWGui , mSpellVisible(true) , mWorldMouseOver(false) , mEnemyHealthTimer(-1) + , mEnemyActorId(-1) , mIsDrowning(false) , mWeaponSpellTimer(0.f) , mDrowningFlashTheta(0.f) @@ -609,7 +610,10 @@ namespace MWGui void HUD::updateEnemyHealthBar() { - MWMechanics::CreatureStats& stats = mEnemy.getClass().getCreatureStats(mEnemy); + MWWorld::Ptr enemy = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mEnemyActorId); + if (enemy.isEmpty()) + return; + MWMechanics::CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); mEnemyHealth->setProgressRange(100); // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) @@ -620,7 +624,7 @@ namespace MWGui { mSpellIcons->updateWidgets(mEffectBox, true); - if (!mEnemy.isEmpty() && mEnemyHealth->getVisible()) + if (mEnemyActorId != -1 && mEnemyHealth->getVisible()) { updateEnemyHealthBar(); } @@ -634,7 +638,7 @@ namespace MWGui void HUD::setEnemy(const MWWorld::Ptr &enemy) { - mEnemy = enemy; + mEnemyActorId = enemy.getClass().getCreatureStats(enemy).getActorId(); mEnemyHealthTimer = 5; if (!mEnemyHealth->getVisible()) mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20)); @@ -644,7 +648,7 @@ namespace MWGui void HUD::resetEnemy() { - mEnemy = MWWorld::Ptr(); + mEnemyActorId = -1; mEnemyHealthTimer = -1; } diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index f451ea4d2..56b0ef7b5 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -104,7 +104,7 @@ namespace MWGui SpellIcons* mSpellIcons; - MWWorld::Ptr mEnemy; + int mEnemyActorId; float mEnemyHealthTimer; bool mIsDrowning; diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index fdaf93039..b86f61034 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -140,26 +140,28 @@ void ItemView::onMouseWheel(MyGUI::Widget *_sender, int _rel) void ItemView::setSize(const MyGUI::IntSize &_value) { + bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setSize(_value); - update(); + if (changed) + update(); } void ItemView::setSize(int _width, int _height) { - Base::setSize(_width, _height); - update(); + setSize(MyGUI::IntSize(_width, _height)); } void ItemView::setCoord(const MyGUI::IntCoord &_value) { + bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setCoord(_value); - update(); + if (changed) + update(); } void ItemView::setCoord(int _left, int _top, int _width, int _height) { - Base::setCoord(_left, _top, _width, _height); - update(); + setCoord(MyGUI::IntCoord(_left, _top, _width, _height)); } void ItemView::registerComponents() diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index b9ddb7daa..b71e64a19 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -259,7 +259,7 @@ struct JournalViewModelImpl : JournalViewModel os << itr->mDayOfMonth << ' ' << MWBase::Environment::get().getWorld()->getMonthName (itr->mMonth) - << " (" << dayStr << " " << (itr->mDay + 1) << ')'; + << " (" << dayStr << " " << (itr->mDay) << ')'; timestamp_buffer = os.str (); } diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index eaa822722..32aaa6e71 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -16,9 +16,10 @@ namespace MWGui { - + const unsigned int LevelupDialog::sMaxCoins = 3; LevelupDialog::LevelupDialog() - : WindowBase("openmw_levelup_dialog.layout") + : WindowBase("openmw_levelup_dialog.layout"), + mCoinCount(sMaxCoins) { getWidget(mOkButton, "OkButton"); getWidget(mClassImage, "ClassImage"); @@ -46,12 +47,10 @@ namespace MWGui mAttributeMultipliers.push_back(t); } - int curX = mMainWidget->getWidth()/2 - (16 + 2) * 1.5; - for (int i=0; i<3; ++i) + for (unsigned int i = 0; i < mCoinCount; ++i) { - MyGUI::ImageBox* image = mMainWidget->createWidget("ImageBox", MyGUI::IntCoord(curX,250,16,16), MyGUI::Align::Default); + MyGUI::ImageBox* image = mCoinBox->createWidget("ImageBox", MyGUI::IntCoord(0,0,16,16), MyGUI::Align::Default); image->setImageTexture ("icons\\tx_goldicon.dds"); - curX += 24+2; mCoins.push_back(image); } @@ -61,15 +60,15 @@ namespace MWGui void LevelupDialog::setAttributeValues() { MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats (player); + MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); - for (int i=0; i<8; ++i) + for (int i = 0; i < 8; ++i) { - int val = creatureStats.getAttribute (i).getBase (); + int val = creatureStats.getAttribute(i).getBase(); if (std::find(mSpentAttributes.begin(), mSpentAttributes.end(), i) != mSpentAttributes.end()) { - val += pcStats.getLevelupAttributeMultiplier (i); + val += pcStats.getLevelupAttributeMultiplier(i); } if (val >= 100) @@ -80,20 +79,21 @@ namespace MWGui } - void LevelupDialog::resetCoins () + void LevelupDialog::resetCoins() { - int curX = 0; - for (int i=0; i<3; ++i) + const int coinSpacing = 10; + int curX = mCoinBox->getWidth()/2 - (coinSpacing*(mCoinCount - 1) + 16*mCoinCount)/2; + for (unsigned int i=0; idetachFromWidget(); image->attachToWidget(mCoinBox); image->setCoord(MyGUI::IntCoord(curX,0,16,16)); - curX += 24+2; + curX += 16+coinSpacing; } } - void LevelupDialog::assignCoins () + void LevelupDialog::assignCoins() { resetCoins(); for (unsigned int i=0; igetPlayerPtr(); - MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats (player); - MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); - - mSpentAttributes.clear(); - resetCoins(); - - setAttributeValues(); + MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player); + MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); const ESM::NPC *playerData = player.get()->mBase; @@ -132,82 +127,111 @@ namespace MWGui const ESM::Class *cls = world->getStore().get().find(playerData->mClass); - // Vanilla uses thief.dds for custom classes. A player with a custom class - // doesn't have mId set, see mwworld/esmstore.hpp where it is initialised as - // "$dynamic0". This check should resolve bug #1260. - // Choosing Stealth specialization and Speed/Agility as attributes. if(world->getStore().get().isDynamic(cls->mId)) { + // Vanilla uses thief.dds for custom classes. + // Choosing Stealth specialization and Speed/Agility as attributes, if possible. Otherwise fall back to first class found. MWWorld::SharedIterator it = world->getStore().get().begin(); for(; it != world->getStore().get().end(); it++) { if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) break; } - mClassImage->setImageTexture ("textures\\levelup\\" + it->mId + ".dds"); + if (it == world->getStore().get().end()) + it = world->getStore().get().begin(); + if (it != world->getStore().get().end()) + cls = &*it; } - else - mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); + + mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); int level = creatureStats.getLevel ()+1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast(level)); std::string levelupdescription; - if(level>20) + if(level > 20) levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default"); else levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+boost::lexical_cast(level)); mLevelDescription->setCaption (levelupdescription); - for (int i=0; i<8; ++i) + unsigned int availableAttributes = 0; + for (int i = 0; i < 8; ++i) { MyGUI::TextBox* text = mAttributeMultipliers[i]; - int mult = pcStats.getLevelupAttributeMultiplier (i); - text->setCaption(mult <= 1 ? "" : "x" + boost::lexical_cast(mult)); + if (pcStats.getAttribute(i).getBase() < 100) + { + mAttributes[i]->setEnabled(true); + availableAttributes++; + + int mult = pcStats.getLevelupAttributeMultiplier (i); + text->setCaption(mult <= 1 ? "" : "x" + boost::lexical_cast(mult)); + } + else + { + mAttributes[i]->setEnabled(false); + + text->setCaption(""); + } } + mCoinCount = std::min(sMaxCoins, availableAttributes); + + for (unsigned int i = 0; i < sMaxCoins; i++) + { + if (i < mCoinCount) + mCoins[i]->attachToWidget(mCoinBox); + else + mCoins[i]->detachFromWidget(); + } + + mSpentAttributes.clear(); + resetCoins(); + + setAttributeValues(); + center(); } - void LevelupDialog::onOkButtonClicked (MyGUI::Widget* sender) + void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); - if (mSpentAttributes.size() < 3) - MWBase::Environment::get().getWindowManager ()->messageBox("#{sNotifyMessage36}"); + if (mSpentAttributes.size() < mCoinCount) + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage36}"); else { // increase attributes - for (int i=0; i<3; ++i) + for (unsigned int i = 0; i < mCoinCount; ++i) { MWMechanics::AttributeValue attribute = pcStats.getAttribute(mSpentAttributes[i]); - attribute.setBase (attribute.getBase () + pcStats.getLevelupAttributeMultiplier (mSpentAttributes[i])); + attribute.setBase(attribute.getBase() + pcStats.getLevelupAttributeMultiplier(mSpentAttributes[i])); if (attribute.getBase() >= 100) attribute.setBase(100); pcStats.setAttribute(mSpentAttributes[i], attribute); } - pcStats.levelUp (); + pcStats.levelUp(); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Levelup); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Levelup); } } - void LevelupDialog::onAttributeClicked (MyGUI::Widget *sender) + void LevelupDialog::onAttributeClicked(MyGUI::Widget *sender) { int attribute = *sender->getUserData(); std::vector::iterator found = std::find(mSpentAttributes.begin(), mSpentAttributes.end(), attribute); if (found != mSpentAttributes.end()) - mSpentAttributes.erase (found); + mSpentAttributes.erase(found); else { - if (mSpentAttributes.size() == 3) - mSpentAttributes[2] = attribute; + if (mSpentAttributes.size() == mCoinCount) + mSpentAttributes[mCoinCount - 1] = attribute; else mSpentAttributes.push_back(attribute); } diff --git a/apps/openmw/mwgui/levelupdialog.hpp b/apps/openmw/mwgui/levelupdialog.hpp index 69afbf089..5ca9c8c75 100644 --- a/apps/openmw/mwgui/levelupdialog.hpp +++ b/apps/openmw/mwgui/levelupdialog.hpp @@ -28,8 +28,11 @@ namespace MWGui std::vector mSpentAttributes; - void onOkButtonClicked (MyGUI::Widget* sender); - void onAttributeClicked (MyGUI::Widget* sender); + unsigned int mCoinCount; + static const unsigned int sMaxCoins; + + void onOkButtonClicked(MyGUI::Widget* sender); + void onAttributeClicked(MyGUI::Widget* sender); void assignCoins(); void resetCoins(); diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 49cc60d8a..0d1a57000 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -117,15 +117,18 @@ void MerchantRepair::exit() void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + // repair MWWorld::Ptr item = *sender->getUserData(); item.getCellRef().setCharge(item.getClass().getItemMaxHealth(item)); + player.getClass().getContainerStore(player).restack(item); + MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1); int price = boost::lexical_cast(sender->getUserString("Price")); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); startRepair(mActor); diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 079564273..9c43b1416 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -165,6 +165,8 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) item.getCellRef().setEnchantmentCharge( std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast(enchantment->mData.mCharge))); + player.getClass().getContainerStore(player).restack(item); + player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0); } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index f4602b064..09d2fc19c 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -157,6 +157,8 @@ namespace MWGui { configureWidgets(mMainWidget); + setTitle("#{sOptions}"); + getWidget(mOkButton, "OkButton"); getWidget(mResolutionList, "ResolutionList"); getWidget(mFullscreenButton, "FullscreenButton"); @@ -174,6 +176,7 @@ namespace MWGui getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mRefractionButton, "RefractionButton"); + getWidget(mDifficultySlider, "DifficultySlider"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); mShaderModeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShaderModeToggled); @@ -227,6 +230,10 @@ namespace MWGui MyGUI::TextBox* fovText; getWidget(fovText, "FovText"); fovText->setCaption("Field of View (" + boost::lexical_cast(int(Settings::Manager::getInt("field of view", "General"))) + ")"); + + MyGUI::TextBox* diffText; + getWidget(diffText, "DifficultyText"); + diffText->setCaptionWithReplacing("#{sDifficulty} (" + boost::lexical_cast(int(Settings::Manager::getInt("difficulty", "Game"))) + ")"); } void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender) @@ -406,6 +413,12 @@ namespace MWGui getWidget(fovText, "FovText"); fovText->setCaption("Field of View (" + boost::lexical_cast(int(value)) + ")"); } + if (scroller == mDifficultySlider) + { + MyGUI::TextBox* diffText; + getWidget(diffText, "DifficultyText"); + diffText->setCaptionWithReplacing("#{sDifficulty} (" + boost::lexical_cast(int(value)) + ")"); + } } else { diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 37f2c8af0..cbfb5cfe1 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -30,6 +30,7 @@ namespace MWGui MyGUI::Button* mVSyncButton; MyGUI::Button* mFPSButton; MyGUI::ScrollBar* mFOVSlider; + MyGUI::ScrollBar* mDifficultySlider; MyGUI::ScrollBar* mAnisotropySlider; MyGUI::ComboBox* mTextureFilteringButton; MyGUI::TextBox* mAnisotropyLabel; diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 6c4e00ae5..b11258f1c 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -321,6 +321,11 @@ namespace MWGui skillValueWidget->_setWidgetState(state); skillValueWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); + // resize dynamically according to text size + int textWidthPlusMargin = skillValueWidget->getTextSize().width + 12; + skillValueWidget->setCoord(coord2.left + coord2.width - textWidthPlusMargin, coord2.top, textWidthPlusMargin, coord2.height); + skillNameWidget->setSize(skillNameWidget->getSize() + MyGUI::IntSize(coord2.width - textWidthPlusMargin, 0)); + mSkillWidgets.push_back(skillNameWidget); mSkillWidgets.push_back(skillValueWidget); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 676a9ee63..e09ff2487 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -547,6 +547,17 @@ namespace MWGui return " (" + boost::lexical_cast(value) + ")"; } + std::string ToolTips::getCellRefString(const MWWorld::CellRef& cellref) + { + std::string ret; + ret += getMiscString(cellref.getOwner(), "Owner"); + ret += getMiscString(cellref.getFaction(), "Faction"); + if (cellref.getFactionRank() > 0) + ret += getValueString(cellref.getFactionRank(), "Rank"); + ret += getMiscString(cellref.getGlobalVariable(), "Global"); + return ret; + } + bool ToolTips::toggleFullHelp() { mFullHelp = !mFullHelp; diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 4e73cc555..8b6174b87 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -66,6 +66,9 @@ namespace MWGui static std::string getCountString(const int value); ///< @return blank string if count is 1, or else " (value)" + static std::string getCellRefString(const MWWorld::CellRef& cellref); + ///< Returns a string containing debug tooltip information about the given cellref. + // these do not create an actual tooltip, but they fill in the data that is required so the tooltip // system knows what to show in case this widget is hovered static void createSkillToolTip(MyGUI::Widget* widget, int skillId); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c0a51311f..c56c2ee94 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -531,4 +531,12 @@ namespace MWGui sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); } } + + void TradeWindow::resetReference() + { + ReferenceInterface::resetReference(); + mItemView->setModel(NULL); + mTradeModel = NULL; + mSortModel = NULL; + } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index cc70f1ae9..b487a8870 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -37,6 +37,7 @@ namespace MWGui virtual void exit(); + virtual void resetReference(); private: ItemView* mItemView; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 865ad1dca..9ebafc90c 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -268,7 +268,7 @@ namespace MWGui mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); trackWindow(mCompanionWindow, "companion"); - mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows"); + mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Overlay"); mHud->setVisible(mHudEnabled); @@ -1529,6 +1529,8 @@ namespace MWGui mCompanionWindow->resetReference(); mConsole->resetReference(); + mSelectedSpell.clear(); + mGuiModes.clear(); MWBase::Environment::get().getInputManager()->changeInputMode(false); updateVisible(); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index afdde6fb0..067ad72bb 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -686,14 +686,6 @@ namespace MWInput return; } - if(MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Dialogue) { //Give access to the main menu when at a choice in dialogue - if(MWBase::Environment::get().getDialogueManager()->isInChoice()) { - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - MWBase::Environment::get().getSoundManager()->pauseSounds (MWBase::SoundManager::Play_TypeSfx); - return; - } - } - if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) //No open GUIs, open up the MainMenu { MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); @@ -707,11 +699,13 @@ namespace MWInput } void InputManager::quickLoad() { - MWBase::Environment::get().getStateManager()->quickLoad(); + if (!MyGUI::InputManager::getInstance().isModalAny()) + MWBase::Environment::get().getStateManager()->quickLoad(); } void InputManager::quickSave() { - MWBase::Environment::get().getStateManager()->quickSave(); + if (!MyGUI::InputManager::getInstance().isModalAny()) + MWBase::Environment::get().getStateManager()->quickSave(); } void InputManager::toggleSpell() { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6ea65275b..8f1209827 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -184,7 +184,7 @@ namespace MWMechanics calculateCreatureStatModifiers (ptr, duration); // fatigue restoration - calculateRestoration(ptr, duration, false); + calculateRestoration(ptr, duration); } void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer) @@ -293,7 +293,7 @@ namespace MWMechanics creatureStats.setFatigue(fatigue); } - void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration, bool sleep) + void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, bool sleep) { if (ptr.getClass().getCreatureStats(ptr).isDead()) return; @@ -331,10 +331,30 @@ namespace MWMechanics float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance); x *= fEndFatigueMult * endurance; + DynamicStat fatigue = stats.getFatigue(); + fatigue.setCurrent (fatigue.getCurrent() + 3600 * x); + stats.setFatigue (fatigue); + } + + void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration) + { + if (ptr.getClass().getCreatureStats(ptr).isDead()) + return; + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); + + int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); + + // restore fatigue + float fFatigueReturnBase = settings.find("fFatigueReturnBase")->getFloat (); + float fFatigueReturnMult = settings.find("fFatigueReturnMult")->getFloat (); + + float x = fFatigueReturnBase + fFatigueReturnMult * endurance; + DynamicStat fatigue = stats.getFatigue(); fatigue.setCurrent (fatigue.getCurrent() + duration * x); stats.setFatigue (fatigue); - } void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) @@ -840,7 +860,7 @@ namespace MWMechanics if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.isHostile()) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - float cutoff = float(esmStore.get().find("iCrimeThreshold")->getInt()); + int cutoff = esmStore.get().find("iCrimeThreshold")->getInt(); // Force dialogue on sight if bounty is greater than the cutoff // In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty) if ( player.getClass().getNpcStats(player).getBounty() >= cutoff @@ -848,7 +868,11 @@ namespace MWMechanics && MWBase::Environment::get().getWorld()->getLOS(ptr, player) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) { - creatureStats.getAiSequence().stack(AiPursue(player), ptr); + static int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->getInt(); + if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier) + MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); + else + creatureStats.getAiSequence().stack(AiPursue(player), ptr); creatureStats.setAlarmed(true); npcStats.setCrimeId(MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId()); } @@ -1029,10 +1053,6 @@ namespace MWMechanics iter->second->update(duration); } - // Kill dead actors, update some variables - - int hostilesCount = 0; // need to know this to play Battle music - for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); @@ -1048,68 +1068,23 @@ namespace MWMechanics stats.setKnockedDownOneFrame(false); stats.setKnockedDownOverOneFrame(true); } + } + + int hostilesCount = 0; // need to know this to play Battle music + + for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + { + const MWWorld::Class &cls = iter->first.getClass(); + CreatureStats &stats = cls.getCreatureStats(iter->first); if(!stats.isDead()) { if (stats.isHostile()) hostilesCount++; - - if(iter->second->isDead()) - { - // Actor has been resurrected. Notify the CharacterController and re-enable collision. - MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, true); - iter->second->resurrect(); - } - - if(!stats.isDead()) - continue; - } - - // If it's the player and God Mode is turned on, keep it alive - if (iter->first.getRefData().getHandle()=="player" && - MWBase::Environment::get().getWorld()->getGodModeState()) - { - MWMechanics::DynamicStat stat (stats.getHealth()); - - if (stat.getModified()<1) - { - stat.setModified(1, 0); - stats.setHealth(stat); - } - stats.resurrect(); - continue; - } - - if (iter->second->kill()) - { - ++mDeathCount[cls.getId(iter->first)]; - - // Make sure spell effects with CasterLinked flag are removed - for (PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) - { - MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); - spells.purge(stats.getActorId()); - } - - // Apply soultrap - if (iter->first.getTypeName() == typeid(ESM::Creature).name()) - { - SoulTrap soulTrap (iter->first); - stats.getActiveSpells().visitEffectSources(soulTrap); - } - - // Reset magic effects and recalculate derived effects - // One case where we need this is to make sure bound items are removed upon death - stats.setMagicEffects(MWMechanics::MagicEffects()); - stats.getActiveSpells().clear(); - calculateCreatureStatModifiers(iter->first, 0); - - MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); - - if (cls.isEssential(iter->first)) - MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); } } + killDeadActors(); + // check if we still have any player enemies to switch music static bool isBattleMusic = false; @@ -1184,10 +1159,78 @@ namespace MWMechanics } } } + + void Actors::killDeadActors() + { + for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + { + const MWWorld::Class &cls = iter->first.getClass(); + CreatureStats &stats = cls.getCreatureStats(iter->first); + + if(!stats.isDead()) + { + if(iter->second->isDead()) + { + // Actor has been resurrected. Notify the CharacterController and re-enable collision. + MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, true); + iter->second->resurrect(); + } + + if(!stats.isDead()) + continue; + } + + // If it's the player and God Mode is turned on, keep it alive + if (iter->first.getRefData().getHandle()=="player" && + MWBase::Environment::get().getWorld()->getGodModeState()) + { + MWMechanics::DynamicStat stat (stats.getHealth()); + + if (stat.getModified()<1) + { + stat.setModified(1, 0); + stats.setHealth(stat); + } + stats.resurrect(); + continue; + } + + if (iter->second->kill()) + { + ++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())]; + + // Make sure spell effects with CasterLinked flag are removed + for (PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) + { + MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); + spells.purge(stats.getActorId()); + } + + // Apply soultrap + if (iter->first.getTypeName() == typeid(ESM::Creature).name()) + { + SoulTrap soulTrap (iter->first); + stats.getActiveSpells().visitEffectSources(soulTrap); + } + + // Reset magic effects and recalculate derived effects + // One case where we need this is to make sure bound items are removed upon death + stats.setMagicEffects(MWMechanics::MagicEffects()); + stats.getActiveSpells().clear(); + calculateCreatureStatModifiers(iter->first, 0); + + MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); + + if (cls.isEssential(iter->first)) + MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); + } + } + } + void Actors::restoreDynamicStats(bool sleep) { for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) - calculateRestoration(iter->first, 3600, sleep); + restoreDynamicStats(iter->first, sleep); } int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const @@ -1281,12 +1324,8 @@ namespace MWMechanics { const MWWorld::Class &cls = iter->getClass(); CreatureStats &stats = cls.getCreatureStats(*iter); - if(!stats.isDead() && stats.getAiSequence().getTypeId() == AiPackage::TypeIdCombat) - { - MWMechanics::AiCombat* package = static_cast(stats.getAiSequence().getActivePackage()); - if(package->getTarget() == actor) - list.push_front(*iter); - } + if (!stats.isDead() && stats.getAiSequence().isInCombat(actor)) + list.push_front(*iter); } return list; } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 4784162f4..4b5d77a7f 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -36,7 +36,7 @@ namespace MWMechanics void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); void calculateNpcStatModifiers (const MWWorld::Ptr& ptr); - void calculateRestoration (const MWWorld::Ptr& ptr, float duration, bool sleep); + void calculateRestoration (const MWWorld::Ptr& ptr, float duration); void updateDrowning (const MWWorld::Ptr& ptr, float duration); @@ -90,12 +90,17 @@ namespace MWMechanics void restoreDynamicStats(bool sleep); ///< If the player is sleeping, this should be called every hour. + void restoreDynamicStats(const MWWorld::Ptr& actor, bool sleep); + int getHoursToRest(const MWWorld::Ptr& ptr) const; ///< Calculate how many hours the given actor needs to rest in order to be fully healed int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. + ///@see MechanicsManager::killDeadActors + void killDeadActors (); + void forceStateUpdate(const MWWorld::Ptr &ptr); void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 2606daa88..c0a97a1b1 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -265,7 +265,7 @@ namespace MWMechanics const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype; - float weapRange; + float weapRange = 1.0f; actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); @@ -300,7 +300,7 @@ namespace MWMechanics else //is creature { weaptype = WeapType_HandToHand; //doesn't matter, should only reflect if it is melee or distant weapon - weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) + weapRange = 150.0f; //TODO: use true attack range (the same problem in Creature::hit) } float rangeAttack; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 3aeeee65a..02f00dfc6 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -72,6 +72,30 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const return true; } +bool AiSequence::isInCombat() const +{ + for(std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + return true; + } + return false; +} + +bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const +{ + for(std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + { + const AiCombat *combat = static_cast(*it); + if (combat->getTarget() == actor) + return true; + } + } + return false; +} + bool AiSequence::canAddTarget(const ESM::Position& actorPos, float distToTarget) const { bool firstCombatFound = false; diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index b789d33cd..bc20dc61b 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -63,6 +63,12 @@ namespace MWMechanics /// Return true and assign target if combat package is currently active, return false otherwise bool getCombatTarget (MWWorld::Ptr &targetActor) const; + /// Is there any combat package? + bool isInCombat () const; + + /// Are we in combat with this particular actor? + bool isInCombat (const MWWorld::Ptr& actor) const; + bool canAddTarget(const ESM::Position& actorPos, float distToTarget) const; ///< Function assumes that actor can have only 1 target apart player diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp new file mode 100644 index 000000000..255decdf7 --- /dev/null +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -0,0 +1,232 @@ +#include "autocalcspell.hpp" + +#include + +#include "../mwworld/esmstore.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + + +namespace MWMechanics +{ + + struct SchoolCaps + { + int mCount; + int mLimit; + bool mReachedLimit; + int mMinCost; + std::string mWeakestSpell; + }; + + std::vector autoCalcNpcSpells(const int *actorSkills, const int *actorAttributes, const ESM::Race* race) + { + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fNPCbaseMagickaMult = gmst.find("fNPCbaseMagickaMult")->getFloat(); + float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + static int iAutoSpellSchoolMax[6]; + static bool init = false; + if (!init) + { + for (int i=0; i<6; ++i) + { + const std::string& gmstName = "iAutoSpell" + schools[i] + "Max"; + iAutoSpellSchoolMax[i] = gmst.find(gmstName)->getInt(); + } + init = true; + } + + std::map schoolCaps; + for (int i=0; i<6; ++i) + { + SchoolCaps caps; + caps.mCount = 0; + caps.mLimit = iAutoSpellSchoolMax[i]; + caps.mReachedLimit = iAutoSpellSchoolMax[i] <= 0; + caps.mMinCost = INT_MAX; + caps.mWeakestSpell.clear(); + schoolCaps[i] = caps; + } + + std::vector selectedSpells; + + const MWWorld::Store &spells = + MWBase::Environment::get().getWorld()->getStore().get(); + + // Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the + // Store must preserve the record ordering as it was in the content files. + for (MWWorld::Store::iterator iter = spells.begin(); iter != spells.end(); ++iter) + { + const ESM::Spell* spell = &*iter; + + if (spell->mData.mType != ESM::Spell::ST_Spell) + continue; + if (!(spell->mData.mFlags & ESM::Spell::F_Autocalc)) + continue; + static const int iAutoSpellTimesCanCast = gmst.find("iAutoSpellTimesCanCast")->getInt(); + if (baseMagicka < iAutoSpellTimesCanCast * spell->mData.mCost) + continue; + + if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell->mId) != race->mPowers.mList.end()) + continue; + + if (!attrSkillCheck(spell, actorSkills, actorAttributes)) + continue; + + int school; + float skillTerm; + calcWeakestSchool(spell, actorSkills, school, skillTerm); + assert(school >= 0 && school < 6); + SchoolCaps& cap = schoolCaps[school]; + + if (cap.mReachedLimit && spell->mData.mCost <= cap.mMinCost) + continue; + + static const float fAutoSpellChance = gmst.find("fAutoSpellChance")->getFloat(); + if (calcAutoCastChance(spell, actorSkills, actorAttributes, school) < fAutoSpellChance) + continue; + + selectedSpells.push_back(spell->mId); + + if (cap.mReachedLimit) + { + std::vector::iterator found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell); + if (found != selectedSpells.end()) + selectedSpells.erase(found); + + cap.mMinCost = INT_MAX; + for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) + { + const ESM::Spell* testSpell = spells.find(*weakIt); + + //int testSchool; + //float dummySkillTerm; + //calcWeakestSchool(testSpell, actorSkills, testSchool, dummySkillTerm); + + // Note: if there are multiple spells with the same cost, we pick the first one we found. + // So the algorithm depends on the iteration order of the outer loop. + if ( + // There is a huge bug here. It is not checked that weakestSpell is of the correct school. + // As result multiple SchoolCaps could have the same mWeakestSpell. Erasing the weakest spell would then fail if another school + // already erased it, and so the number of spells would often exceed the sum of limits. + // This bug cannot be fixed without significantly changing the results of the spell autocalc, which will not have been playtested. + //testSchool == school && + testSpell->mData.mCost < cap.mMinCost) + { + cap.mMinCost = testSpell->mData.mCost; + cap.mWeakestSpell = testSpell->mId; + } + } + } + else + { + cap.mCount += 1; + if (cap.mCount == cap.mLimit) + cap.mReachedLimit = true; + + if (spell->mData.mCost < cap.mMinCost) + { + cap.mWeakestSpell = spell->mId; + cap.mMinCost = spell->mData.mCost; + } + } + } + + return selectedSpells; + } + + bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes) + { + const std::vector& effects = spell->mEffects.mList; + for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) + { + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); + static const int iAutoSpellAttSkillMin = MWBase::Environment::get().getWorld()->getStore().get().find("iAutoSpellAttSkillMin")->getInt(); + + if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) + { + assert (effectIt->mSkill >= 0 && effectIt->mSkill < ESM::Skill::Length); + if (actorSkills[effectIt->mSkill] < iAutoSpellAttSkillMin) + return false; + } + + if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)) + { + assert (effectIt->mAttribute >= 0 && effectIt->mAttribute < ESM::Attribute::Length); + if (actorAttributes[effectIt->mAttribute] < iAutoSpellAttSkillMin) + return false; + } + } + + return true; + } + + ESM::Skill::SkillEnum mapSchoolToSkill(int school) + { + std::map schoolSkillMap; // maps spell school to skill id + schoolSkillMap[0] = ESM::Skill::Alteration; + schoolSkillMap[1] = ESM::Skill::Conjuration; + schoolSkillMap[3] = ESM::Skill::Illusion; + schoolSkillMap[2] = ESM::Skill::Destruction; + schoolSkillMap[4] = ESM::Skill::Mysticism; + schoolSkillMap[5] = ESM::Skill::Restoration; + assert(schoolSkillMap.find(school) != schoolSkillMap.end()); + return schoolSkillMap[school]; + } + + void calcWeakestSchool (const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm) + { + float minChance = FLT_MAX; + + const ESM::EffectList& effects = spell->mEffects; + for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) + { + const ESM::ENAMstruct& effect = *it; + float x = effect.mDuration; + + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) + x = std::max(1.f, x); + + x *= 0.1f * magicEffect->mData.mBaseCost; + x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); + x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; + if (effect.mRange == ESM::RT_Target) + x *= 1.5f; + + static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find("fEffectCostMult")->getFloat(); + x *= fEffectCostMult; + + float s = 2.f * actorSkills[mapSchoolToSkill(magicEffect->mData.mSchool)]; + if (s - x < minChance) + { + minChance = s - x; + effectiveSchool = magicEffect->mData.mSchool; + skillTerm = s; + } + } + } + + float calcAutoCastChance(const ESM::Spell *spell, const int *actorSkills, const int *actorAttributes, int effectiveSchool) + { + if (spell->mData.mType != ESM::Spell::ST_Spell) + return 100.f; + + if (spell->mData.mFlags & ESM::Spell::F_Always) + return 100.f; + + float skillTerm; + if (effectiveSchool != -1) + skillTerm = 2.f * actorSkills[mapSchoolToSkill(effectiveSchool)]; + else + calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this + + float castChance = skillTerm - spell->mData.mCost + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck]; + return castChance; + } +} diff --git a/apps/openmw/mwmechanics/autocalcspell.hpp b/apps/openmw/mwmechanics/autocalcspell.hpp new file mode 100644 index 000000000..1912c75c4 --- /dev/null +++ b/apps/openmw/mwmechanics/autocalcspell.hpp @@ -0,0 +1,31 @@ +#ifndef OPENMW_AUTOCALCSPELL_H +#define OPENMW_AUTOCALCSPELL_H + +#include +#include + +#include +#include +#include + +namespace MWMechanics +{ + +/// Contains algorithm for calculating an NPC's spells based on stats +/// @note We might want to move this code to a component later, so the editor can use it for preview purposes + +std::vector autoCalcNpcSpells(const int* actorSkills, const int* actorAttributes, const ESM::Race* race); + +// Helpers + +bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes); + +ESM::Skill::SkillEnum mapSchoolToSkill(int school); + +void calcWeakestSchool(const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm); + +float calcAutoCastChance(const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, int effectiveSchool); + +} + +#endif diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2c5d68ceb..638feeef3 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -361,9 +361,10 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat * beginning. */ int mode = ((movement == mCurrentMovement) ? 2 : 1); + mMovementAnimationControlled = true; + mAnimation->disable(mCurrentMovement); mCurrentMovement = movement; - mMovementAnimVelocity = 0.0f; if(!mCurrentMovement.empty()) { float vel, speedmult = 1.0f; @@ -383,16 +384,18 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) { - mMovementAnimVelocity = vel; speedmult = mMovementSpeed / vel; } else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed else if (mMovementSpeed > 0.0f) + { // The first person anims don't have any velocity to calculate a speed multiplier from. // We use the third person velocities instead. // FIXME: should be pulled from the actual animation, but it is not presently loaded. speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); + mMovementAnimationControlled = false; + } mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); } @@ -506,6 +509,7 @@ void CharacterController::playDeath(float startpoint, CharacterState death) mJumpState = JumpState_None; mAnimation->disable(mCurrentJump); mCurrentJump = ""; + mMovementAnimationControlled = true; mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, false, 1.0f, "start", "stop", startpoint, 0); @@ -547,7 +551,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mIdleState(CharState_None) , mMovementState(CharState_None) , mMovementSpeed(0.0f) - , mMovementAnimVelocity(0.0f) + , mMovementAnimationControlled(true) , mDeathState(CharState_None) , mHitState(CharState_None) , mUpperBodyState(UpperCharState_Nothing) @@ -570,10 +574,14 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if (cls.hasInventoryStore(mPtr)) { getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); + if (mWeaponType != WeapType_None) + { + mUpperBodyState = UpperCharState_WeapEquiped; + getWeaponGroup(mWeaponType, mCurrentWeapon); + } + if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) { - getWeaponGroup(mWeaponType, mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; mAnimation->showWeapons(true); mAnimation->setWeaponGroup(mCurrentWeapon); } @@ -599,6 +607,8 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if(mDeathState == CharState_None) refreshCurrentAnims(mIdleState, mMovementState, true); + + mAnimation->runAnimation(0.f); } CharacterController::~CharacterController() @@ -1241,6 +1251,7 @@ void CharacterController::update(float duration) if (inwater || flying) cls.getCreatureStats(mPtr).land(); + bool inJump = true; if(!onground && !flying && !inwater) { // In the air (either getting up —ascending part of jump— or falling). @@ -1330,6 +1341,8 @@ void CharacterController::update(float duration) mJumpState = JumpState_None; vec.z = 0.0f; + inJump = false; + if(std::abs(vec.x/2.0f) > std::abs(vec.y)) { if(vec.x > 0.0f) @@ -1391,6 +1404,8 @@ void CharacterController::update(float duration) forcestateupdate = updateCreatureState() || forcestateupdate; refreshCurrentAnims(idlestate, movestate, forcestateupdate); + if (inJump) + mMovementAnimationControlled = false; if (!mSkipAnim) { @@ -1402,7 +1417,7 @@ void CharacterController::update(float duration) else //avoid z-rotating for knockdown world->rotateObject(mPtr, rot.x, rot.y, 0.0f, true); - if (mMovementAnimVelocity == 0) + if (!mMovementAnimationControlled) world->queueMovement(mPtr, vec); } else @@ -1446,7 +1461,7 @@ void CharacterController::update(float duration) } // Update movement - if(mMovementAnimVelocity > 0) + if(mMovementAnimationControlled && mPtr.getClass().isActor()) world->queueMovement(mPtr, moved); } else if (mAnimation) diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 59c20db8f..b1e1738bd 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -147,7 +147,7 @@ class CharacterController CharacterState mMovementState; std::string mCurrentMovement; float mMovementSpeed; - float mMovementAnimVelocity; + bool mMovementAnimationControlled; CharacterState mDeathState; std::string mCurrentDeath; diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 6feb5879b..c13eac98c 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -10,6 +10,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/difficultyscaling.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -54,11 +55,24 @@ namespace MWMechanics if (!blocker.getClass().hasInventoryStore(blocker)) return false; - if (blocker.getClass().getCreatureStats(blocker).getKnockedDown() - || blocker.getClass().getCreatureStats(blocker).getHitRecovery()) + MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker); + + if (blockerStats.getKnockedDown() // Used for both knockout or knockdown + || blockerStats.getHitRecovery() + || blockerStats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0) + return false; + + // Don't block when in spellcasting state (shield is equipped, but not visible) + if (blockerStats.getDrawState() == DrawState_Spell) return false; MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker); + + // Don't block when in hand-to-hand combat (shield is equipped, but not visible) + if (blockerStats.getDrawState() == DrawState_Weapon && + inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight) == inv.end()) + return false; + MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name()) return false; @@ -72,17 +86,6 @@ namespace MWMechanics if (angle.valueDegrees() > gmst.find("fCombatBlockRightAngle")->getFloat()) return false; - MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker); - - // Don't block when in spellcasting state (shield is equipped, but not visible) - if (blockerStats.getDrawState() == DrawState_Spell) - return false; - - // Don't block when in hand-to-hand combat (shield is equipped, but not visible) - if (blockerStats.getDrawState() == DrawState_Weapon && - inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight) == inv.end()) - return false; - MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2 * blockerStats.getAttribute(ESM::Attribute::Agility).getModified() @@ -144,11 +147,7 @@ namespace MWMechanics float resistance = std::min(100.f, stats.getMagicEffects().get(ESM::MagicEffect::ResistNormalWeapons).mMagnitude - stats.getMagicEffects().get(ESM::MagicEffect::WeaknessToNormalWeapons).mMagnitude); - float multiplier = 0; - if (resistance >= 0) - multiplier = 1 - resistance / 100.f; - else - multiplier = -(resistance-100) / 100.f; + float multiplier = 1.f - resistance / 100.f; if (!(weapon.get()->mBase->mData.mFlags & ESM::Weapon::Silver || weapon.get()->mBase->mData.mFlags & ESM::Weapon::Magical)) @@ -210,16 +209,10 @@ namespace MWMechanics damage *= fDamageStrengthBase + (attackerStats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1); + if(attacker.getRefData().getHandle() == "player") attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); - bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim); - if(!detected) - { - damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); - MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); - MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); - } if (victim.getClass().getCreatureStats(victim).getKnockedDown()) damage *= gmst.find("fCombatKODamageMult")->getFloat(); @@ -256,4 +249,50 @@ namespace MWMechanics return hitchance; } + void applyElementalShields(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim) + { + for (int i=0; i<3; ++i) + { + float magnitude = victim.getClass().getCreatureStats(victim).getMagicEffects().get(ESM::MagicEffect::FireShield+i).mMagnitude; + + if (!magnitude) + continue; + + CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); + float saveTerm = attacker.getClass().getSkill(attacker, ESM::Skill::Destruction) + + 0.2f * attackerStats.getAttribute(ESM::Attribute::Willpower).getModified() + + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified(); + + int fatigueMax = attackerStats.getFatigue().getModified(); + int fatigueCurrent = attackerStats.getFatigue().getCurrent(); + + float normalisedFatigue = fatigueMax==0 ? 1 : std::max (0.0f, static_cast (fatigueCurrent)/fatigueMax); + + saveTerm *= 1.25f * normalisedFatigue; + + float roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + float x = std::max(0.f, saveTerm - roll); + + int element = ESM::MagicEffect::FireDamage; + if (i == 1) + element = ESM::MagicEffect::ShockDamage; + if (i == 2) + element = ESM::MagicEffect::FrostDamage; + + float elementResistance = MWMechanics::getEffectResistanceAttribute(element, &attackerStats.getMagicEffects()); + + x = std::min(100.f, x + elementResistance); + + static const float fElementalShieldMult = MWBase::Environment::get().getWorld()->getStore().get().find("fElementalShieldMult")->getFloat(); + x = fElementalShieldMult * magnitude * (1.f - 0.01f * x); + + // Note swapped victim and attacker, since the attacker takes the damage here. + x = scaleDamage(x, victim, attacker); + + MWMechanics::DynamicStat health = attackerStats.getHealth(); + health.setCurrent(health.getCurrent() - x); + attackerStats.setHealth(health); + } + } + } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index bc58227bf..eb89cb820 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -19,6 +19,9 @@ void projectileHit (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MW /// Get the chance (in percent) for \a attacker to successfully hit \a victim with a given weapon skill value float getHitChance (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, int skillValue); +/// Applies damage to attacker based on the victim's elemental shields. +void applyElementalShields(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); + } #endif diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index af35a109a..71217dbb9 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -8,6 +8,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" namespace MWMechanics { @@ -191,6 +192,12 @@ namespace MWMechanics mDied = true; mDead = true; + + if (mDied) + // Must increase death count immediately. There are scripts that use getDeadCount as reaction to onDeath + // and rely on the increased value. + // Would be more appropriate to use a killActor(actor) function, but we don't have access to the Ptr in CreatureStats. + MWBase::Environment::get().getMechanicsManager()->killDeadActors(); } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index b365a0b89..a83da4249 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -208,7 +208,8 @@ namespace MWMechanics float getEvasion() const; void setKnockedDown(bool value); - ///Returns true for the entire duration of the actor being knocked down + /// Returns true for the entire duration of the actor being knocked down or knocked out, + /// including transition animations (falling down & standing up) bool getKnockedDown() const; void setKnockedDownOneFrame(bool value); ///Returns true only for the first frame of the actor being knocked out; used for "onKnockedOut" command diff --git a/apps/openmw/mwmechanics/difficultyscaling.cpp b/apps/openmw/mwmechanics/difficultyscaling.cpp new file mode 100644 index 000000000..05ab12ccd --- /dev/null +++ b/apps/openmw/mwmechanics/difficultyscaling.cpp @@ -0,0 +1,38 @@ +#include "difficultyscaling.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwworld/esmstore.hpp" + +#include + +float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim) +{ + const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + // [-100, 100] + int difficultySetting = Settings::Manager::getInt("difficulty", "Game"); + + static const float fDifficultyMult = MWBase::Environment::get().getWorld()->getStore().get().find("fDifficultyMult")->getFloat(); + + float difficultyTerm = 0.01f * difficultySetting; + + float x = 0; + if (victim == player) + { + if (difficultyTerm > 0) + x = fDifficultyMult * difficultyTerm; + else + x = difficultyTerm / fDifficultyMult; + } + else if (attacker == player) + { + if (difficultyTerm > 0) + x = -difficultyTerm / fDifficultyMult; + else + x = fDifficultyMult * (-difficultyTerm); + } + + damage *= 1 + x; + return damage; +} diff --git a/apps/openmw/mwmechanics/difficultyscaling.hpp b/apps/openmw/mwmechanics/difficultyscaling.hpp new file mode 100644 index 000000000..168cf1055 --- /dev/null +++ b/apps/openmw/mwmechanics/difficultyscaling.hpp @@ -0,0 +1,12 @@ +#ifndef OPENMW_MWMECHANICS_DIFFICULTYSCALING_H +#define OPENMW_MWMECHANICS_DIFFICULTYSCALING_H + +namespace MWWorld +{ + class Ptr; +} + +/// Scales damage dealt to an actor based on difficulty setting +float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); + +#endif diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 5f73e8acd..05ce1c7ae 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -14,9 +14,11 @@ namespace MWMechanics { /// Call when \a actor has got in contact with \a carrier (e.g. hit by him, or loots him) + /// @param actor The actor that will potentially catch diseases. Currently only the player can catch diseases. + /// @param carrier The disease carrier. inline void diseaseContact (MWWorld::Ptr actor, MWWorld::Ptr carrier) { - if (!carrier.getClass().isActor()) + if (!carrier.getClass().isActor() || actor != MWBase::Environment::get().getWorld()->getPlayerPtr()) return; float fDiseaseXferChance = @@ -27,25 +29,47 @@ namespace MWMechanics for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); - if (spell->mData.mType == ESM::Spell::ST_Disease - || spell->mData.mType == ESM::Spell::ST_Blight) - { - float roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (roll < fDiseaseXferChance) - { - // Contracted disease! - actor.getClass().getCreatureStats(actor).getSpells().add(it->first); - if (actor.getRefData().getHandle() == "player") - { - std::string msg = "sMagicContractDisease"; - msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->getString(); - if (msg.find("%s") != std::string::npos) - msg.replace(msg.find("%s"), 2, spell->mName); - MWBase::Environment::get().getWindowManager()->messageBox(msg); - } + if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId)) + continue; + + bool hasCorprusEffect = false; + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) + { + if (effectIt->mEffectID == ESM::MagicEffect::Corprus) + { + hasCorprusEffect = true; + break; } } + + float resist = 0.f; + if (hasCorprusEffect) + resist = 1.f - 0.01 * (actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::ResistCorprusDisease).mMagnitude + - actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::WeaknessToCorprusDisease).mMagnitude); + else if (spell->mData.mType == ESM::Spell::ST_Disease) + resist = 1.f - 0.01 * (actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude + - actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::WeaknessToCommonDisease).mMagnitude); + else if (spell->mData.mType == ESM::Spell::ST_Blight) + resist = 1.f - 0.01 * (actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude + - actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::WeaknessToBlightDisease).mMagnitude); + else + continue; + + int x = fDiseaseXferChance * 100 * resist; + float roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 10000; // [0, 9999] + + if (roll < x) + { + // Contracted disease! + actor.getClass().getCreatureStats(actor).getSpells().add(it->first); + + std::string msg = "sMagicContractDisease"; + msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->getString(); + if (msg.find("%s") != std::string::npos) + msg.replace(msg.find("%s"), 2, spell->mName); + MWBase::Environment::get().getWindowManager()->messageBox(msg); + } } } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 6e515142d..861d5e110 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -19,6 +19,7 @@ #include #include "spellcasting.hpp" +#include "autocalcspell.hpp" namespace { @@ -33,10 +34,19 @@ namespace if (!faction.empty() && ptr.getClass().isNpc()) { const std::map& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks(); - if (factions.find(Misc::StringUtils::lowerCase(faction)) == factions.end()) + std::map::const_iterator found = factions.find(Misc::StringUtils::lowerCase(faction)); + if (found == factions.end() + || found->second < item.getCellRef().getFactionRank()) isFactionOwned = true; } + const std::string& globalVariable = item.getCellRef().getGlobalVariable(); + if (!globalVariable.empty() && MWBase::Environment::get().getWorld()->getGlobalInt(Misc::StringUtils::lowerCase(globalVariable)) == 1) + { + isOwned = false; + isFactionOwned = false; + } + if (!item.getCellRef().getOwner().empty()) victim = MWBase::Environment::get().getWorld()->searchPtr(item.getCellRef().getOwner(), true); @@ -155,19 +165,6 @@ namespace MWMechanics npcStats.getSkill (index).setBase ( npcStats.getSkill (index).getBase() + bonus); } - - if (i==1) - { - // Major skill - add starting spells for this skill if existing - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - MWWorld::Store::iterator it = store.get().begin(); - for (; it != store.get().end(); ++it) - { - if (it->mData.mFlags & ESM::Spell::F_PCStart - && spellSchoolToSkill(getSpellSchool(&*it, ptr)) == index) - creatureStats.getSpells().add(it->mId); - } - } } } @@ -190,6 +187,87 @@ namespace MWMechanics } } + // F_PCStart spells + static const float fPCbaseMagickaMult = esmStore.get().find("fPCbaseMagickaMult")->getFloat(); + + float baseMagicka = fPCbaseMagickaMult * creatureStats.getAttribute(ESM::Attribute::Intelligence).getBase(); + bool reachedLimit = false; + const ESM::Spell* weakestSpell = NULL; + int minCost = INT_MAX; + + std::vector selectedSpells; + + const ESM::Race* race = NULL; + if (mRaceSelected) + race = esmStore.get().find(player->mRace); + + int skills[ESM::Skill::Length]; + for (int i=0; i &spells = + esmStore.get(); + for (MWWorld::Store::iterator iter = spells.begin(); iter != spells.end(); ++iter) + { + const ESM::Spell* spell = &*iter; + + if (spell->mData.mType != ESM::Spell::ST_Spell) + continue; + if (!(spell->mData.mFlags & ESM::Spell::F_PCStart)) + continue; + if (reachedLimit && spell->mData.mCost <= minCost) + continue; + if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell->mId) != race->mPowers.mList.end()) + continue; + if (baseMagicka < spell->mData.mCost) + continue; + + static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->getFloat(); + if (calcAutoCastChance(spell, skills, attributes, -1) < fAutoPCSpellChance) + continue; + + if (!attrSkillCheck(spell, skills, attributes)) + continue; + + selectedSpells.push_back(spell->mId); + + if (reachedLimit) + { + std::vector::iterator it = std::find(selectedSpells.begin(), selectedSpells.end(), weakestSpell->mId); + if (it != selectedSpells.end()) + selectedSpells.erase(it); + + minCost = INT_MAX; + for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) + { + const ESM::Spell* testSpell = esmStore.get().find(*weakIt); + if (testSpell->mData.mCost < minCost) + { + minCost = testSpell->mData.mCost; + weakestSpell = testSpell; + } + } + } + else + { + if (spell->mData.mCost < minCost) + { + weakestSpell = spell; + minCost = weakestSpell->mData.mCost; + } + static const unsigned int iAutoPCSpellMax = esmStore.get().find("iAutoPCSpellMax")->getInt(); + if (selectedSpells.size() == iAutoPCSpellMax) + reachedLimit = true; + } + } + + for (std::vector::iterator it = selectedSpells.begin(); it != selectedSpells.end(); ++it) + creatureStats.getSpells().add(*it); + // forced update and current value adjustments mActors.updateActor (ptr, 0); @@ -584,6 +662,10 @@ namespace MWMechanics return mActors.countDeaths (id); } + void MechanicsManager::killDeadActors() + { + mActors.killDeadActors(); + } void MechanicsManager::getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 596e6887e..365e24067 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -160,6 +160,10 @@ namespace MWMechanics /// @param bias Can be used to add an additional aggression bias towards the target, /// making it more likely for the function to return true. virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0, bool ignoreDistance=false); + + /// Usually done once a frame, but can be invoked manually in time-critical situations. + /// This will increase the death count for any actors that were killed. + virtual void killDeadActors(); }; } diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 9f2c851cf..6d6f889ed 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -57,10 +57,13 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) charge = std::min(charge + y, itemToRepair.getClass().getItemMaxHealth(itemToRepair)); itemToRepair.getCellRef().setCharge(charge); + // attempt to re-stack item, in case it was fully repaired + MWWorld::ContainerStoreIterator stacked = player.getClass().getContainerStore(player).restack(itemToRepair); + // set the OnPCRepair variable on the item's script - std::string script = itemToRepair.getClass().getScript(itemToRepair); + std::string script = stacked->getClass().getScript(itemToRepair); if(script != "") - itemToRepair.getRefData().getLocals().setVarByInt(script, "onpcrepair", 1); + stacked->getRefData().getLocals().setVarByInt(script, "onpcrepair", 1); // increase skill player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 6467730dd..67da99200 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -92,7 +92,7 @@ namespace MWMechanics x *= 0.1 * magicEffect->mData.mBaseCost; x *= 0.5 * (it->mMagnMin + it->mMagnMax); x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost; - if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) + if (it->mRange == ESM::RT_Target) x *= 1.5; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( "fEffectCostMult")->getFloat(); @@ -148,6 +148,27 @@ namespace MWMechanics return school; } + float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects) + { + short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); + short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); + + float resistance = 0; + if (resistanceEffect != -1) + resistance += actorEffects->get(resistanceEffect).mMagnitude; + if (weaknessEffect != -1) + resistance -= actorEffects->get(weaknessEffect).mMagnitude; + + if (effectId == ESM::MagicEffect::FireDamage) + resistance += actorEffects->get(ESM::MagicEffect::FireShield).mMagnitude; + if (effectId == ESM::MagicEffect::ShockDamage) + resistance += actorEffects->get(ESM::MagicEffect::LightningShield).mMagnitude; + if (effectId == ESM::MagicEffect::FrostDamage) + resistance += actorEffects->get(ESM::MagicEffect::FrostShield).mMagnitude; + + return resistance; + } + float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell, const MagicEffects* effects) { @@ -163,16 +184,7 @@ namespace MWMechanics float resisted = 0; if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) { - - short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); - short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); - - float resistance = 0; - if (resistanceEffect != -1) - resistance += magicEffects->get(resistanceEffect).mMagnitude; - if (weaknessEffect != -1) - resistance -= magicEffects->get(weaknessEffect).mMagnitude; - + float resistance = getEffectResistanceAttribute(effectId, magicEffects); float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); @@ -484,7 +496,11 @@ namespace MWMechanics if (effectId == ESM::MagicEffect::Lock) { if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude + { + if (caster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); target.getCellRef().setLockLevel(magnitude); + } } else if (effectId == ESM::MagicEffect::Open) { @@ -492,12 +508,14 @@ namespace MWMechanics { if (target.getCellRef().getLockLevel() > 0) { - //Door not already unlocked MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); if (!caster.isEmpty() && caster.getClass().isActor()) MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); + + if (caster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); } - target.getCellRef().setLockLevel(-abs(target.getCellRef().getLockLevel())); //unlocks the door + target.getCellRef().setLockLevel(-abs(target.getCellRef().getLockLevel())); } else MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index b526a4353..2de187ae4 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -35,6 +35,11 @@ namespace MWMechanics int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); + /// Get the resistance attribute against an effect for a given actor. This will add together + /// ResistX and Weakness to X effects relevant against the given effect. + float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects); + + /// Get the effective resistance against an effect casted by the given actor in the given spell (optional). /// @return >=100 for fully resisted. can also return negative value for damage amplification. /// @param effects Override the actor's current magicEffects. Useful if there are effects currently /// being applied (but not applied yet) that should also be considered. diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 9e683cc15..4580bae70 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -149,6 +149,8 @@ namespace MWRender mViewModeToggleQueued = true; return; } + else + mViewModeToggleQueued = false; mFirstPersonView = !mFirstPersonView; processViewChange(); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 62907fcc3..271cbbfe1 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -340,7 +340,9 @@ Ogre::TexturePtr LocalMap::createFogOfWarTexture(const std::string &texName) sFogOfWarResolution, sFogOfWarResolution, 0, PF_A8R8G8B8, - TU_DYNAMIC_WRITE_ONLY); + TU_DYNAMIC_WRITE_ONLY, + this // ManualResourceLoader required if the texture contents are lost (due to lost devices nonsense that can occur with D3D) + ); } return tex; @@ -457,6 +459,30 @@ bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interi return alpha < 200; } +void LocalMap::loadResource(Ogre::Resource* resource) +{ + std::string resourceName = resource->getName(); + size_t pos = resourceName.find("_fog"); + if (pos != std::string::npos) + resourceName = resourceName.substr(0, pos); + if (mBuffers.find(resourceName) == mBuffers.end()) + { + // create a buffer to use for dynamic operations + std::vector buffer; + + // initialize to (0, 0, 0, 1) + buffer.resize(sFogOfWarResolution*sFogOfWarResolution, 0xFF000000); + mBuffers[resourceName] = buffer; + } + + std::vector& buffer = mBuffers[resourceName]; + + Ogre::Texture* tex = dynamic_cast(resource); + tex->createInternalResources(); + memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); + tex->getBuffer()->unlock(); +} + void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation) { if (sFogOfWarSkip != 0) diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index babf7224e..1572800e5 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace MWWorld { @@ -23,12 +24,14 @@ namespace MWRender /// /// \brief Local map rendering /// - class LocalMap + class LocalMap : public Ogre::ManualResourceLoader { public: LocalMap(OEngine::Render::OgreRenderer*, MWRender::RenderingManager* rendering); ~LocalMap(); + virtual void loadResource(Ogre::Resource* resource); + /** * Clear all savegame-specific data (i.e. fog of war textures) */ diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 03e74697c..409e27388 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -1088,7 +1088,7 @@ public: void close() { } - bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height) + bool update() { return false; } }; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index a4c74be6b..4e0257d82 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -147,6 +147,14 @@ namespace MWScript } ptr.getClass().lock (ptr, lockLevel); + + // Instantly reset door to closed state + // This is done when using Lock in scripts, but not when using Lock spells. + if (ptr.getTypeName() == typeid(ESM::Door).name()) + { + MWBase::Environment::get().getWorld()->activateDoor(ptr, 0); + MWBase::Environment::get().getWorld()->localRotateObject(ptr, 0, 0, 0); + } } }; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 3860257ad..a041049ca 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -86,16 +86,24 @@ namespace MWScript float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); float az = Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees(); + MWWorld::LocalRotation localRot = ptr.getRefData().getLocalRotation(); + if (axis == "x") { + localRot.rot[0] = 0; + ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az); } else if (axis == "y") { + localRot.rot[1] = 0; + ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az); } else if (axis == "z") { + localRot.rot[2] = 0; + ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle); } else diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index f16d8e3d1..cdf08e6ed 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -88,6 +88,16 @@ namespace MWWorld return mCellRef.mOwner; } + std::string CellRef::getGlobalVariable() const + { + return mCellRef.mGlobalVariable; + } + + int CellRef::getFactionRank() const + { + return mCellRef.mFactionRank; + } + void CellRef::setOwner(const std::string &owner) { if (owner != mCellRef.mOwner) diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 689671c02..577655739 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -61,6 +61,11 @@ namespace MWWorld std::string getOwner() const; void setOwner(const std::string& owner); + // Name of a global variable. If the global variable is set to '1', using the object is temporarily allowed + // even if it has an Owner field. + // Used by bed rent scripts to allow the player to use the bed for the duration of the rent. + std::string getGlobalVariable() const; + // ID of creature trapped in this soul gem std::string getSoul() const; void setSoul(const std::string& soul); @@ -70,6 +75,9 @@ namespace MWWorld std::string getFaction() const; void setFaction (const std::string& faction); + // PC faction rank required to use the item. Sometimes is -1, which means "any rank". + int getFactionRank() const; + // Lock level for doors and containers // Positive for a locked door. 0 for a door that was never locked. // For an unlocked door, it is set to -(previous locklevel) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index e330ddaee..18ebd82db 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -141,6 +141,34 @@ void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container) remove(ptr, ptr.getRefData().getCount()-1, container); } +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::Ptr& item) +{ + MWWorld::ContainerStoreIterator retval = end(); + for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + { + if (item == *iter) + { + retval = iter; + break; + } + } + + if (retval == end()) + throw std::runtime_error("item is not from this container"); + + for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + { + if (stacks(*iter, item)) + { + iter->getRefData().setCount(iter->getRefData().getCount() + item.getRefData().getCount()); + item.getRefData().setCount(0); + retval = iter; + break; + } + } + return retval; +} + bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { const MWWorld::Class& cls1 = ptr1.getClass(); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 7c81bdb6e..6d9d7a6bb 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -134,6 +134,11 @@ namespace MWWorld void unstack (const Ptr& ptr, const Ptr& container); ///< Unstack an item in this container. The item's count will be set to 1, then a new stack will be added with (origCount-1). + MWWorld::ContainerStoreIterator restack (const MWWorld::Ptr& item); + ///< Attempt to re-stack an item in this container. + /// If a compatible stack is found, the item's count is added to that stack, then the original is deleted. + /// @return If the item was stacked, return the stack, otherwise return the old (untouched) item. + /// @return How many items with refID \a id are in this container? int count (const std::string& id); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 2eb8aeb46..891440023 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -512,28 +512,21 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c // empty this slot mSlots[slot] = end(); - // restack the previously equipped item with other (non-equipped) items - for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + if (it->getRefData().getCount()) { - if (stacks(*iter, *it)) - { - iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount()); - it->getRefData().setCount(0); - retval = iter; - break; - } - } + retval = restack(*it); - if (actor.getRefData().getHandle() == "player") - { - // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared - const std::string& script = it->getClass().getScript(*it); - if (script != "") - (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); - - if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it)) + if (actor.getRefData().getHandle() == "player") { - mSelectedEnchantItem = end(); + // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared + const std::string& script = it->getClass().getScript(*it); + if (script != "") + (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); + + if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it)) + { + mSelectedEnchantItem = end(); + } } } @@ -633,8 +626,15 @@ void MWWorld::InventoryStore::rechargeItems(float duration) static float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get().find( "fMagicItemRechargePerSecond")->getFloat(); - it->first->getCellRef().setEnchantmentCharge(std::min (it->first->getCellRef().getEnchantmentCharge() + fMagicItemRechargePerSecond * duration, - it->second)); + if (it->first->getCellRef().getEnchantmentCharge() <= it->second) + { + it->first->getCellRef().setEnchantmentCharge(std::min (it->first->getCellRef().getEnchantmentCharge() + fMagicItemRechargePerSecond * duration, + it->second)); + + // attempt to restack when fully recharged + if (it->first->getCellRef().getEnchantmentCharge() == it->second) + it->first = restack(*it->first); + } } } diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index b77257e47..0becd7524 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -28,15 +28,13 @@ namespace MWWorld cellRef.mRefNum.mContentFile = -1; cellRef.mRefID = name; cellRef.mScale = 1; - cellRef.mFactIndex = 0; + cellRef.mFactionRank = 0; cellRef.mCharge = -1; cellRef.mGoldValue = 1; cellRef.mEnchantmentCharge = -1; cellRef.mTeleport = false; cellRef.mLockLevel = 0; cellRef.mReferenceBlocked = 0; - cellRef.mFltv = 0; - cellRef.mNam0 = 0; LiveCellRef ref(cellRef, base); diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 201393761..daad5b0e6 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -364,7 +364,7 @@ namespace MWWorld continue; // velocity updated, calculate nextpos again } - if(newPosition.squaredDistance(nextpos) > 0.00000001*0.00000001) + if(newPosition.squaredDistance(nextpos) > 0.0001) { // trace to where character would go if there were no obstructions tracer.doTrace(colobj, newPosition, nextpos, engine); diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 3e7e7a5f9..9a442387b 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -209,8 +209,6 @@ namespace MWWorld } void setUp() { - //std::sort(mStatic.begin(), mStatic.end(), RecordCmp()); - mShared.clear(); mShared.reserve(mStatic.size()); typename std::map::iterator it = mStatic.begin(); @@ -675,18 +673,15 @@ namespace MWWorld } void setUp() { - //typedef std::vector::iterator Iterator; typedef DynamicExt::iterator ExtIterator; typedef std::map::iterator IntIterator; - //std::sort(mInt.begin(), mInt.end(), RecordCmp()); mSharedInt.clear(); mSharedInt.reserve(mInt.size()); for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) { mSharedInt.push_back(&(it->second)); } - //std::sort(mExt.begin(), mExt.end(), ExtCmp()); mSharedExt.clear(); mSharedExt.reserve(mExt.size()); for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) { @@ -1147,6 +1142,37 @@ namespace MWWorld } }; + + // Specialisation for ESM::Spell to preserve record order as it was in the content files. + // The NPC spell autocalc code heavily depends on this order. + // We could also do this in the base class, but it's usually not a good idea to depend on record order. + template<> + inline void Store::clearDynamic() + { + // remove the dynamic part of mShared + mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); + mDynamic.clear(); + } + + template<> + inline void Store::load(ESM::ESMReader &esm, const std::string &id) { + std::string idLower = Misc::StringUtils::lowerCase(id); + + std::pair inserted = mStatic.insert(std::make_pair(idLower, ESM::Spell())); + if (inserted.second) + mShared.push_back(&mStatic[idLower]); + + inserted.first->second.mId = idLower; + inserted.first->second.load(esm); + } + + template<> + inline void Store::setUp() + { + // remove the dynamic part of mShared + mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); + } + } //end namespace #endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 33405b4d8..9b747582f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1123,24 +1123,30 @@ namespace MWWorld ptr.getRefData().setPosition(pos); - mWorldScene->updateObjectRotation(ptr); + if(ptr.getRefData().getBaseNode() == 0) + return; + + if (ptr.getClass().isActor()) + mWorldScene->updateObjectRotation(ptr); + else + mWorldScene->updateObjectLocalRotation(ptr); } void World::localRotateObject (const Ptr& ptr, float x, float y, float z) { + LocalRotation rot = ptr.getRefData().getLocalRotation(); + rot.rot[0]=Ogre::Degree(x).valueRadians(); + rot.rot[1]=Ogre::Degree(y).valueRadians(); + rot.rot[2]=Ogre::Degree(z).valueRadians(); + + wrap(rot.rot[0]); + wrap(rot.rot[1]); + wrap(rot.rot[2]); + + ptr.getRefData().setLocalRotation(rot); + if (ptr.getRefData().getBaseNode() != 0) { - LocalRotation rot = ptr.getRefData().getLocalRotation(); - rot.rot[0]=Ogre::Degree(x).valueRadians(); - rot.rot[1]=Ogre::Degree(y).valueRadians(); - rot.rot[2]=Ogre::Degree(z).valueRadians(); - - wrap(rot.rot[0]); - wrap(rot.rot[1]); - wrap(rot.rot[2]); - - ptr.getRefData().setLocalRotation(rot); - mWorldScene->updateObjectLocalRotation(ptr); } } @@ -1980,11 +1986,12 @@ namespace MWWorld mDoorStates[door] = state; } - void World::activateDoor(const Ptr &door, bool open) + void World::activateDoor(const Ptr &door, int state) { - int state = open ? 1 : 2; door.getClass().setDoorState(door, state); mDoorStates[door] = state; + if (state == 0) + mDoorStates.erase(door); } bool World::getPlayerStandingOn (const MWWorld::Ptr& object) @@ -2313,15 +2320,10 @@ namespace MWWorld } // If this is a power, check if it was already used in the last 24h - if (!fail && spell->mData.mType == ESM::Spell::ST_Power) + if (!fail && spell->mData.mType == ESM::Spell::ST_Power && !stats.getSpells().canUsePower(spell->mId)) { - if (stats.getSpells().canUsePower(spell->mId)) - stats.getSpells().usePower(spell->mId); - else - { - message = "#{sPowerAlreadyUsed}"; - fail = true; - } + message = "#{sPowerAlreadyUsed}"; + fail = true; } // Reduce mana @@ -2354,6 +2356,10 @@ namespace MWWorld if (!selectedSpell.empty()) { const ESM::Spell* spell = getStore().get().search(selectedSpell); + + // A power can be used once per 24h + if (spell->mData.mType == ESM::Spell::ST_Power) + stats.getSpells().usePower(spell->mId); cast.cast(spell); } @@ -2583,9 +2589,15 @@ namespace MWWorld float fCrimeGoldDiscountMult = getStore().get().find("fCrimeGoldDiscountMult")->getFloat(); float fCrimeGoldTurnInMult = getStore().get().find("fCrimeGoldTurnInMult")->getFloat(); - int discount = bounty*fCrimeGoldDiscountMult; + int discount = bounty * fCrimeGoldDiscountMult; int turnIn = bounty * fCrimeGoldTurnInMult; + if (bounty > 0) + { + discount = std::max(1, discount); + turnIn = std::max(1, turnIn); + } + mGlobalVariables["pchascrimegold"].setInteger((bounty <= playerGold) ? 1 : 0); mGlobalVariables["pchasgolddiscount"].setInteger((discount <= playerGold) ? 1 : 0); @@ -2649,6 +2661,8 @@ namespace MWWorld { mGoToJail = false; + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); + MWWorld::Ptr player = getPlayerPtr(); teleportToClosestMarker(player, "prisonmarker"); int bounty = player.getClass().getNpcStats(player).getBounty(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 08d7eb42d..dda442963 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -473,8 +473,9 @@ namespace MWWorld /// open or close a non-teleport door (depending on current state) virtual void activateDoor(const MWWorld::Ptr& door); - /// open or close a non-teleport door as specified - virtual void activateDoor(const MWWorld::Ptr& door, bool open); + /// update movement state of a non-teleport door as specified + /// @param state see MWClass::setDoorState + virtual void activateDoor(const MWWorld::Ptr& door, int state); virtual bool getPlayerStandingOn (const MWWorld::Ptr& object); ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object); ///< @return true if any actor is standing on \a object diff --git a/apps/openmw_test_suite/components/misc/test_stringops.cpp b/apps/openmw_test_suite/components/misc/test_stringops.cpp index 44587c445..55fe0e0c2 100644 --- a/apps/openmw_test_suite/components/misc/test_stringops.cpp +++ b/apps/openmw_test_suite/components/misc/test_stringops.cpp @@ -12,68 +12,3 @@ struct StringOpsTest : public ::testing::Test { } }; - -TEST_F(StringOpsTest, begins_matching) -{ - ASSERT_TRUE(Misc::begins("abc", "a")); - ASSERT_TRUE(Misc::begins("abc", "ab")); - ASSERT_TRUE(Misc::begins("abc", "abc")); - ASSERT_TRUE(Misc::begins("abcd", "abc")); -} - -TEST_F(StringOpsTest, begins_not_matching) -{ - ASSERT_FALSE(Misc::begins("abc", "b")); - ASSERT_FALSE(Misc::begins("abc", "bc")); - ASSERT_FALSE(Misc::begins("abc", "bcd")); - ASSERT_FALSE(Misc::begins("abc", "abcd")); -} - -TEST_F(StringOpsTest, ibegins_matching) -{ - ASSERT_TRUE(Misc::ibegins("Abc", "a")); - ASSERT_TRUE(Misc::ibegins("aBc", "ab")); - ASSERT_TRUE(Misc::ibegins("abC", "abc")); - ASSERT_TRUE(Misc::ibegins("abcD", "abc")); -} - -TEST_F(StringOpsTest, ibegins_not_matching) -{ - ASSERT_FALSE(Misc::ibegins("abc", "b")); - ASSERT_FALSE(Misc::ibegins("abc", "bc")); - ASSERT_FALSE(Misc::ibegins("abc", "bcd")); - ASSERT_FALSE(Misc::ibegins("abc", "abcd")); -} - -TEST_F(StringOpsTest, ends_matching) -{ - ASSERT_TRUE(Misc::ends("abc", "c")); - ASSERT_TRUE(Misc::ends("abc", "bc")); - ASSERT_TRUE(Misc::ends("abc", "abc")); - ASSERT_TRUE(Misc::ends("abcd", "abcd")); -} - -TEST_F(StringOpsTest, ends_not_matching) -{ - ASSERT_FALSE(Misc::ends("abc", "b")); - ASSERT_FALSE(Misc::ends("abc", "ab")); - ASSERT_FALSE(Misc::ends("abc", "bcd")); - ASSERT_FALSE(Misc::ends("abc", "abcd")); -} - -TEST_F(StringOpsTest, iends_matching) -{ - ASSERT_TRUE(Misc::iends("Abc", "c")); - ASSERT_TRUE(Misc::iends("aBc", "bc")); - ASSERT_TRUE(Misc::iends("abC", "abc")); - ASSERT_TRUE(Misc::iends("abcD", "abcd")); -} - -TEST_F(StringOpsTest, iends_not_matching) -{ - ASSERT_FALSE(Misc::iends("abc", "b")); - ASSERT_FALSE(Misc::iends("abc", "ab")); - ASSERT_FALSE(Misc::iends("abc", "bcd")); - ASSERT_FALSE(Misc::iends("abc", "abcd")); -} - diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b8ebb84b1..8f9fb17af 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -72,7 +72,7 @@ add_component_dir (translation add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain - quadtreenode chunk world defaultworld storage material buffercache defs backgroundloader + quadtreenode chunk world defaultworld storage material buffercache defs ) add_component_dir (loadinglistener diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index f5bc2369a..e76930e81 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -23,6 +23,7 @@ ContentSelectorModel::ContentModel::ContentModel(QObject *parent) : void ContentSelectorModel::ContentModel::setEncoding(const QString &encoding) { + mEncoding = encoding; if (encoding == QLatin1String("win1252")) mCodec = QTextCodec::codecForName("windows-1252"); @@ -449,7 +450,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) try { ESM::ESMReader fileReader; ToUTF8::Utf8Encoder encoder = - ToUTF8::calculateEncoding(QString(mCodec->name()).toStdString()); + ToUTF8::calculateEncoding(mEncoding.toStdString()); fileReader.setEncoder(&encoder); fileReader.open(dir.absoluteFilePath(path).toStdString()); diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 6d147bce9..7b2000b51 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -64,6 +64,7 @@ namespace ContentSelectorModel ContentFileList mFiles; QHash mCheckStates; QTextCodec *mCodec; + QString mEncoding; public: diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 84c638d9c..29d26d013 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -6,10 +6,12 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) { - // NAM0 sometimes appears here, sometimes further on - mNam0 = 0; + // According to Hrnchamd, this does not belong to the actual ref. Instead, it is a marker indicating that + // the following refs are part of a "temp refs" section. A temp ref is not being tracked by the moved references system. + // Its only purpose is a performance optimization for "immovable" things. We don't need this, and it's problematic anyway, + // because any item can theoretically be moved by a script. if (esm.isNextSub ("NAM0")) - esm.getHT (mNam0); + esm.skipHSub(); if (wideRefNum) esm.getHNT (mRefNum, "FRMR", 8); @@ -27,12 +29,12 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) esm.getHNOT (mScale, "XSCL"); mOwner = esm.getHNOString ("ANAM"); - mGlob = esm.getHNOString ("BNAM"); + mGlobalVariable = esm.getHNOString ("BNAM"); mSoul = esm.getHNOString ("XSOL"); mFaction = esm.getHNOString ("CNAM"); - mFactIndex = -2; - esm.getHNOT (mFactIndex, "INDX"); + mFactionRank = -2; + esm.getHNOT (mFactionRank, "INDX"); mGoldValue = 1; mCharge = -1; @@ -60,20 +62,14 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) mKey = esm.getHNOString ("KNAM"); mTrap = esm.getHNOString ("TNAM"); - mFltv = 0; esm.getHNOT (mReferenceBlocked, "UNAM"); - esm.getHNOT (mFltv, "FLTV"); + if (esm.isNextSub("FLTV")) // no longer used + esm.skipHSub(); esm.getHNOT(mPos, "DATA", 24); - // Number of references in the cell? Maximum once in each cell, - // but not always at the beginning, and not always right. In other - // words, completely useless. - // Update: Well, maybe not completely useless. This might actually be - // number_of_references + number_of_references_moved_here_Across_boundaries, - // and could be helpful for collecting these weird moved references. - if (esm.isNextSub ("NAM0")) - esm.getHT (mNam0); + if (esm.isNextSub("NAM0")) + esm.skipHSub(); } void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const @@ -90,12 +86,12 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons } esm.writeHNOCString("ANAM", mOwner); - esm.writeHNOCString("BNAM", mGlob); + esm.writeHNOCString("BNAM", mGlobalVariable); esm.writeHNOCString("XSOL", mSoul); esm.writeHNOCString("CNAM", mFaction); - if (mFactIndex != -2) { - esm.writeHNT("INDX", mFactIndex); + if (mFactionRank != -2) { + esm.writeHNT("INDX", mFactionRank); } if (mEnchantmentCharge != -1) @@ -127,14 +123,8 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons if (mReferenceBlocked != -1) esm.writeHNT("UNAM", mReferenceBlocked); - if (!inInventory && mFltv != 0) - esm.writeHNT("FLTV", mFltv); - if (!inInventory) esm.writeHNT("DATA", mPos, 24); - - if (!inInventory && mNam0 != 0) - esm.writeHNT("NAM0", mNam0); } void ESM::CellRef::blank() @@ -144,10 +134,10 @@ void ESM::CellRef::blank() mRefID.clear(); mScale = 1; mOwner.clear(); - mGlob.clear(); + mGlobalVariable.clear(); mSoul.clear(); mFaction.clear(); - mFactIndex = -1; + mFactionRank = -2; mCharge = 0; mEnchantmentCharge = 0; mGoldValue = 0; @@ -156,8 +146,6 @@ void ESM::CellRef::blank() mKey.clear(); mTrap.clear(); mReferenceBlocked = 0; - mFltv = 0; - mNam0 = 0; mTeleport = false; for (int i=0; i<3; ++i) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index b87578120..9c57061b0 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -35,12 +35,13 @@ namespace ESM float mScale; // Scale applied to mesh - // The NPC that owns this object (and will get angry if you steal - // it) + // The NPC that owns this object (and will get angry if you steal it) std::string mOwner; - // I have no idea, looks like a link to a global variable? - std::string mGlob; + // Name of a global variable. If the global variable is set to '1', using the object is temporarily allowed + // even if it has an Owner field. + // Used by bed rent scripts to allow the player to use the bed for the duration of the rent. + std::string mGlobalVariable; // ID of creature trapped in this soul gem std::string mSoul; @@ -49,9 +50,8 @@ namespace ESM // you take it and are not a faction member) std::string mFaction; - // INDX might be PC faction rank required to use the item? Sometimes - // is -1, which I assume means "any rank". - int mFactIndex; + // PC faction rank required to use the item. Sometimes is -1, which means "any rank". + int mFactionRank; // For weapon or armor, this is the remaining item health. // For tools (lockpicks, probes, repair hammer) it is the remaining uses. @@ -82,12 +82,6 @@ namespace ESM // -1 is not blocked, otherwise it is blocked. signed char mReferenceBlocked; - // Occurs in Tribunal.esm, eg. in the cell "Mournhold, Plaza - // Brindisi Dorom", where it has the value 100. Also only for - // activators. - int mFltv; - int mNam0; - // Position and rotation of this object within the cell Position mPos; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 0a9a36109..37f0cc63c 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -33,6 +33,9 @@ void ESM::CreatureStats::load (ESMReader &esm) mAlarmed = false; esm.getHNOT (mAlarmed, "ALRM"); + mAttacked = false; + esm.getHNOT (mAttacked, "ATKD"); + mHostile = false; esm.getHNOT (mHostile, "HOST"); @@ -142,6 +145,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mAlarmed) esm.writeHNT ("ALRM", mAlarmed); + if (mAttacked) + esm.writeHNT ("ATKD", mAttacked); + if (mHostile) esm.writeHNT ("HOST", mHostile); diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 83864569f..0a25fce84 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -58,7 +58,7 @@ void Cell::load(ESMReader &esm, bool saveContext) void Cell::loadCell(ESMReader &esm, bool saveContext) { - mNAM0 = 0; + mRefIdCounter = 0; if (mData.mFlags & Interior) { @@ -92,7 +92,7 @@ void Cell::loadCell(ESMReader &esm, bool saveContext) esm.getHNOT(mMapColor, "NAM5"); } if (esm.isNextSub("NAM0")) { - esm.getHT(mNAM0); + esm.getHT(mRefIdCounter); } if (saveContext) { @@ -150,8 +150,8 @@ void Cell::save(ESMWriter &esm) const esm.writeHNT("NAM5", mMapColor); } - if (mNAM0 != 0) - esm.writeHNT("NAM0", mNAM0); + if (mRefIdCounter != 0) + esm.writeHNT("NAM0", mRefIdCounter); } void Cell::restore(ESMReader &esm, int iCtx) const @@ -220,7 +220,7 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) mWater = 0; mWaterInt = false; mMapColor = 0; - mNAM0 = 0; + mRefIdCounter = 0; mData.mFlags = 0; mData.mX = 0; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index fb4b6b28a..5f889a55b 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -94,7 +94,10 @@ struct Cell float mWater; // Water level bool mWaterInt; int mMapColor; - int mNAM0; + // Counter for RefIds. This is only used during content file editing and has no impact on gameplay. + // It prevents overwriting previous refIDs, even if they were deleted. + // as that would collide with refs when a content file is upgraded. + int mRefIdCounter; // References "leased" from another cell (i.e. a different cell // introduced this ref, and it has been moved here by a plugin) diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index e5b851bf0..2fe9fe3c1 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -10,8 +10,6 @@ namespace ESM void NPC::load(ESMReader &esm) { - //mNpdt52.mGold = -10; - mPersistent = esm.getRecordFlags() & 0x0400; mModel = esm.getHNOString("MODL"); @@ -63,7 +61,6 @@ void NPC::load(ESMReader &esm) } } mAiPackage.load(esm); - esm.skipRecord(); } void NPC::save(ESMWriter &esm) const { diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index cbf5366c4..4bd2210ec 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -27,8 +27,8 @@ struct Spell enum Flags { - F_Autocalc = 1, - F_PCStart = 2, + F_Autocalc = 1, // Can be selected by NPC spells auto-calc + F_PCStart = 2, // Can be selected by player spells auto-calc F_Always = 4 // Casting always succeeds }; diff --git a/components/misc/stringops.cpp b/components/misc/stringops.cpp index 0bc8e290a..0f801e554 100644 --- a/components/misc/stringops.cpp +++ b/components/misc/stringops.cpp @@ -12,59 +12,6 @@ namespace Misc { -bool begins(const char* str1, const char* str2) -{ - while(*str2) - { - if(*str1 == 0 || *str1 != *str2) return false; - - str1++; - str2++; - } - return true; -} - -bool ends(const char* str1, const char* str2) -{ - int len1 = strlen(str1); - int len2 = strlen(str2); - - if(len1 < len2) return false; - - return strcmp(str2, str1+len1-len2) == 0; -} - -// True if the given chars match, case insensitive -static bool icmp(char a, char b) -{ - if(a >= 'A' && a <= 'Z') - a += 'a' - 'A'; - if(b >= 'A' && b <= 'Z') - b += 'a' - 'A'; - - return a == b; -} - -bool ibegins(const char* str1, const char* str2) -{ - while(*str2) - { - if(*str1 == 0 || !icmp(*str1,*str2)) return false; - - str1++; - str2++; - } - return true; -} - -bool iends(const char* str1, const char* str2) -{ - int len1 = strlen(str1); - int len2 = strlen(str2); - - if(len1 < len2) return false; - - return strcasecmp(str2, str1+len1-len2) == 0; -} +std::locale StringUtils::mLocale = std::locale::classic(); } diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index d41463cfc..04dedb072 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -4,15 +4,18 @@ #include #include #include +#include namespace Misc { class StringUtils { + + static std::locale mLocale; struct ci { - bool operator()(int x, int y) const { - return std::tolower(x) < std::tolower(y); + bool operator()(char x, char y) const { + return std::tolower(x, StringUtils::mLocale) < std::tolower(y, StringUtils::mLocale); } }; @@ -28,7 +31,7 @@ public: std::string::const_iterator xit = x.begin(); std::string::const_iterator yit = y.begin(); for (; xit != x.end(); ++xit, ++yit) { - if (std::tolower(*xit) != std::tolower(*yit)) { + if (std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) { return false; } } @@ -42,7 +45,7 @@ public: for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len) { int res = *xit - *yit; - if(res != 0 && std::tolower(*xit) != std::tolower(*yit)) + if(res != 0 && std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) return (res > 0) ? 1 : -1; } if(len > 0) @@ -57,12 +60,8 @@ public: /// Transforms input string to lower case w/o copy static std::string &toLower(std::string &inout) { - std::transform( - inout.begin(), - inout.end(), - inout.begin(), - (int (*)(int)) std::tolower - ); + for (unsigned int i=0; isetEmissionRate(partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom/2)); - emitter->setTimeToLive(partctrl->lifetime, - partctrl->lifetime + partctrl->lifetimeRandom); + emitter->setTimeToLive(std::max(0.f, partctrl->lifetime), + std::max(0.f, partctrl->lifetime + partctrl->lifetimeRandom)); emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x)); emitter->setParameter("height", Ogre::StringConverter::toString(partctrl->offsetRandom.y)); emitter->setParameter("depth", Ogre::StringConverter::toString(partctrl->offsetRandom.z)); diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp index a1433a669..47dce774f 100644 --- a/components/nifogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -448,12 +448,14 @@ public: if(life_time-particle_time < mGrowTime) { Ogre::Real scale = (life_time-particle_time) / mGrowTime; + assert (scale >= 0); width *= scale; height *= scale; } if(particle_time < mFadeTime) { Ogre::Real scale = particle_time / mFadeTime; + assert (scale >= 0); width *= scale; height *= scale; } @@ -479,12 +481,14 @@ public: if(life_time-particle_time < mGrowTime) { Ogre::Real scale = (life_time-particle_time) / mGrowTime; + assert (scale >= 0); width *= scale; height *= scale; } if(particle_time < mFadeTime) { Ogre::Real scale = particle_time / mFadeTime; + assert (scale >= 0); width *= scale; height *= scale; } diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp index 77dbcb1ee..515c0875a 100644 --- a/components/ogreinit/ogreinit.cpp +++ b/components/ogreinit/ogreinit.cpp @@ -193,7 +193,7 @@ namespace OgreInit pluginDir = Ogre::macFrameworksPath(); #endif #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX - pluginDir = OGRE_PLUGIN_DIR_REL; + pluginDir = OGRE_PLUGIN_DIR; #endif } Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); diff --git a/components/terrain/backgroundloader.cpp b/components/terrain/backgroundloader.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 59a9aff80..c53cf62b5 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -4,6 +4,7 @@ #include #include #include +#include /* This file contains the code to translate from WINDOWS-1252 (native charset used in English version of Morrowind) to UTF-8. The library @@ -329,8 +330,10 @@ ToUTF8::FromType ToUTF8::calculateEncoding(const std::string& encodingName) return ToUTF8::WINDOWS_1250; else if (encodingName == "win1251") return ToUTF8::WINDOWS_1251; - else + else if (encodingName == "win1252") return ToUTF8::WINDOWS_1252; + else + throw std::runtime_error(std::string("Unknown encoding '") + encodingName + std::string("', see openmw --help for available options.")); } std::string ToUTF8::encodingUsingMessage(const std::string& encodingName) @@ -339,6 +342,8 @@ std::string ToUTF8::encodingUsingMessage(const std::string& encodingName) return "Using Central and Eastern European font encoding."; else if (encodingName == "win1251") return "Using Cyrillic font encoding."; - else + else if (encodingName == "win1252") return "Using default (English) font encoding."; + else + throw std::runtime_error(std::string("Unknown encoding '") + encodingName + std::string("', see openmw --help for available options.")); } diff --git a/extern/shiny/Main/MaterialInstance.cpp b/extern/shiny/Main/MaterialInstance.cpp index b8032d681..128cc593b 100644 --- a/extern/shiny/Main/MaterialInstance.cpp +++ b/extern/shiny/Main/MaterialInstance.cpp @@ -163,7 +163,7 @@ namespace sh mTexUnits.push_back(texUnit); // set texture unit indices (required by GLSL) - if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && mFactory->getCurrentLanguage () == Language_GLSL) + if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && (mFactory->getCurrentLanguage () == Language_GLSL || mFactory->getCurrentLanguage() == Language_GLSLES)) { pass->setTextureUnitIndex (foundVertex ? GPT_Vertex : GPT_Fragment, texIt->getName(), i); diff --git a/files/mygui/openmw_levelup_dialog.layout b/files/mygui/openmw_levelup_dialog.layout index 0b12d7a05..07e1e560b 100644 --- a/files/mygui/openmw_levelup_dialog.layout +++ b/files/mygui/openmw_levelup_dialog.layout @@ -23,7 +23,7 @@ - + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 9ecae465c..5945c68d7 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -1,7 +1,7 @@ - + @@ -12,47 +12,86 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -63,7 +102,7 @@ - + @@ -74,7 +113,7 @@ - + @@ -85,28 +124,6 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/files/mygui/openmw_travel_window.layout b/files/mygui/openmw_travel_window.layout index 683d47fe7..30b470e08 100644 --- a/files/mygui/openmw_travel_window.layout +++ b/files/mygui/openmw_travel_window.layout @@ -27,7 +27,7 @@ - + diff --git a/files/opencs/Lightbulb-48.png b/files/opencs/Lightbulb-48.png new file mode 100644 index 000000000..c7a45528e Binary files /dev/null and b/files/opencs/Lightbulb-48.png differ diff --git a/files/opencs/Moon-48.png b/files/opencs/Moon-48.png new file mode 100644 index 000000000..016b030f2 Binary files /dev/null and b/files/opencs/Moon-48.png differ diff --git a/files/opencs/Sun-48.png b/files/opencs/Sun-48.png new file mode 100644 index 000000000..dbdede349 Binary files /dev/null and b/files/opencs/Sun-48.png differ diff --git a/files/settings-default.cfg b/files/settings-default.cfg index fac2a0f97..ab9de47a4 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -170,6 +170,8 @@ always run = false # Always use the most powerful attack when striking with a weapon (chop, slash or thrust) best attack = false +difficulty = 0 + [Saves] character = # Save when resting diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp index 383d37640..028192e9f 100644 --- a/libs/openengine/gui/manager.cpp +++ b/libs/openengine/gui/manager.cpp @@ -135,28 +135,6 @@ public: setRenderWindow(_window); setSceneManager(_scene); - // ADDED - sh::MaterialInstance* mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/NoTexture"); - sh::Factory::getInstance()._ensureMaterial("MyGUI/NoTexture", "Default"); - mVertexProgramNoTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) - ->getVertexProgram()->_getBindingDelegate(); - - mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/OneTexture"); - sh::Factory::getInstance()._ensureMaterial("MyGUI/OneTexture", "Default"); - mVertexProgramOneTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) - ->getVertexProgram()->_getBindingDelegate(); - - mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/NoTexture"); - sh::Factory::getInstance()._ensureMaterial("MyGUI/NoTexture", "Default"); - mFragmentProgramNoTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) - ->getFragmentProgram()->_getBindingDelegate(); - - mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/OneTexture"); - sh::Factory::getInstance()._ensureMaterial("MyGUI/OneTexture", "Default"); - mFragmentProgramOneTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) - ->getFragmentProgram()->_getBindingDelegate(); - - MYGUI_PLATFORM_LOG(Info, getClassTypeName() << " successfully initialized"); mIsInitialise = true; @@ -359,6 +337,30 @@ public: } } + void initShaders() + { + // ADDED + sh::MaterialInstance* mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/NoTexture"); + sh::Factory::getInstance()._ensureMaterial("MyGUI/NoTexture", "Default"); + mVertexProgramNoTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) + ->getVertexProgram()->_getBindingDelegate(); + + mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/OneTexture"); + sh::Factory::getInstance()._ensureMaterial("MyGUI/OneTexture", "Default"); + mVertexProgramOneTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) + ->getVertexProgram()->_getBindingDelegate(); + + mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/NoTexture"); + sh::Factory::getInstance()._ensureMaterial("MyGUI/NoTexture", "Default"); + mFragmentProgramNoTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) + ->getFragmentProgram()->_getBindingDelegate(); + + mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/OneTexture"); + sh::Factory::getInstance()._ensureMaterial("MyGUI/OneTexture", "Default"); + mFragmentProgramOneTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) + ->getFragmentProgram()->_getBindingDelegate(); + } + void doRender(IVertexBuffer* _buffer, ITexture* _texture, size_t _count) { if (getManualRender()) @@ -368,6 +370,8 @@ public: } // ADDED + if (!mVertexProgramNoTexture) + initShaders(); if (_texture) { diff --git a/readme.txt b/readme.txt index 92cb35f31..bafca48a4 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.30.0 +Version: 0.31.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org @@ -96,6 +96,188 @@ Allowed options: CHANGELOG +0.31.0 + +Bug #245: Cloud direction and weather systems differ from Morrowind +Bug #275: Local Map does not always show objects that span multiple cells +Bug #538: Update CenterOnCell (COC) function behavior +Bug #618: Local and World Map Textures are sometimes Black +Bug #640: Water behaviour at night +Bug #668: OpenMW doesn't support non-latin paths on Windows +Bug #746: OpenMW doesn't check if the background music was already played +Bug #747: Door is stuck if cell is left before animation finishes +Bug #772: Disabled statics are visible on map +Bug #829: OpenMW uses up all available vram, when playing for extended time +Bug #869: Dead bodies don't collide with anything +Bug #894: Various character creation issues +Bug #897/#1369: opencs Segmentation Fault after "new" or "load" +Bug #899: Various jumping issues +Bug #952: Reflection effects are one frame delayed +Bug #993: Able to interact with world during Wait/Rest dialog +Bug #995: Dropped items can be placed inside the wall +Bug #1008: Corpses always face up upon reentering the cell +Bug #1035: Random colour patterns appearing in automap +Bug #1037: Footstep volume issues +Bug #1047: Creation of wrong links in dialogue window +Bug #1129: Summoned creature time life duration seems infinite +Bug #1134: Crimes can be committed against hostile NPCs +Bug #1136: Creature run speed formula is incorrect +Bug #1150: Weakness to Fire doesn't apply to Fire Damage in the same spell +Bug #1155: NPCs killing each other +Bug #1166: Bittercup script still does not work +Bug #1178: .bsa file names are case sensitive. +Bug #1179: Crash after trying to load game after being killed +Bug #1180: Changing footstep sound location +Bug #1196: Jumping not disabled when showing messageboxes +Bug #1202: "strange" keys are not shown in binding menu, and are not saved either, but works +Bug #1217: Container content changes based on the current position of the mouse +Bug #1234: Loading/saving issues with dynamic records +Bug #1277: Text pasted into the console appears twice +Bug #1284: Crash on New Game +Bug #1303: It's possible to skip the chargen +Bug #1304: Slaughterfish should not detect the player unless the player is in the water +Bug #1311: Editor: deleting Record Filter line does not reset the filter +Bug #1324: ERROR: ESM Error: String table overflow when loading Animated Morrowind.esp +Bug #1328: Editor: Bogus Filter created when dragging multiple records to filter bar of non-applicable table +Bug #1331: Walking/running sound persist after killing NPC`s that are walking/running. +Bug #1334: Previously equipped items not shown as unequipped after attempting to sell them. +Bug #1335: Actors ignore vertical axis when deciding to attack +Bug #1338: Unknown toggle option for shadows +Bug #1339: "Ashlands Region" is visible when beginning new game during "Loading Area" process +Bug #1340: Guards prompt Player with punishment options after resisting arrest with another guard. +Bug #1348: Regression: Bug #1098 has returned with a vengeance +Bug #1349: [TR] TR_Data mesh tr_ex_imp_gatejamb01 cannot be activated +Bug #1352: Disabling an ESX file does not disable dependent ESX files +Bug #1355: CppCat Checks OpenMW +Bug #1356: Incorrect voice type filtering for sleep interrupts +Bug #1357: Restarting the game clears saves +Bug #1360: Seyda Neen silk rider dialog problem +Bug #1361: Some lights don't work +Bug #1364: It is difficult to bind "Mouse 1" to an action in the options menu +Bug #1370: Animation compilation mod does not work properly +Bug #1371: SL_Pick01.nif from third party fails to load in openmw, but works in Vanilla +Bug #1373: When stealing in front of Sellus Gravius cannot exit the dialog +Bug #1378: Installs to /usr/local are not working +Bug #1380: Loading a save file fail if one of the content files is disabled +Bug #1382: "getHExact() size mismatch" crash on loading official plugin "Siege at Firemoth.esp" +Bug #1386: Arkngthand door will not open +Bug #1388: Segfault when modifying View Distance in Menu options +Bug #1389: Crash when loading a save after dying +Bug #1390: Apostrophe characters not displayed [French version] +Bug #1391: Custom made icon background texture for magical weapons and stuff isn't scaled properly on GUI. +Bug #1393: Coin icon during the level up dialogue are off of the background +Bug #1394: Alt+F4 doesn't work on Win version +Bug #1395: Changing rings switches only the last one put on +Bug #1396: Pauldron parts aren't showing when the robe is equipped +Bug #1402: Dialogue of some shrines have wrong button orientation +Bug #1403: Items are floating in the air when they're dropped onto dead bodies. +Bug #1404: Forearms are not rendered on Argonian females +Bug #1407: Alchemy allows making potions from two of the same item +Bug #1408: "Max sale" button gives you all the items AND all the trader's gold +Bug #1409: Rest "Until Healed" broken for characters with stunted magicka. +Bug #1412: Empty travel window opens while playing through start game +Bug #1413: Save game ignores missing writing permission +Bug #1414: The Underground 2 ESM Error +Bug #1416: Not all splash screens in the Splash directory are used +Bug #1417: Loading saved game does not terminate +Bug #1419: Skyrim: Home of the Nords error +Bug #1422: ClearInfoActor +Bug #1423: ForceGreeting closes existing dialogue windows +Bug #1425: Cannot load save game +Bug #1426: Read skill books aren't stored in savegame +Bug #1427: Useless items can be set under hotkeys +Bug #1429: Text variables in journal +Bug #1432: When attacking friendly NPC, the crime is reported and bounty is raised after each swing +Bug #1435: Stealing priceless items is without punishment +Bug #1437: Door marker at Jobasha's Rare Books is spawning PC in the air +Bug #1440: Topic selection menu should be wider +Bug #1441: Dropping items on the rug makes them inaccessible +Bug #1442: When dropping and taking some looted items, bystanders consider that as a crime +Bug #1444: Arrows and bolts are not dropped where the cursor points +Bug #1445: Security trainers offering acrobatics instead +Bug #1447: Character dash not displayed, French edition +Bug #1448: When the player is killed by the guard while having a bounty on his head, the guard dialogue opens over and over instead of loading dialogue +Bug #1454: Script error in SkipTutorial +Bug #1456: Bad lighting when using certain Morrowind.ini generated by MGE +Bug #1457: Heart of Lorkan comes after you when attacking it +Bug #1458: Modified Keybindings are not remembered +Bug #1459: Dura Gra-Bol doesn't respond to PC attack +Bug #1462: Interior cells not loaded with Morrowind Patch active +Bug #1469: Item tooltip should show the base value, not real value +Bug #1477: Death count is not stored in savegame +Bug #1478: AiActivate does not trigger activate scripts +Bug #1481: Weapon not rendered when partially submerged in water +Bug #1483: Enemies are attacking even while dying +Bug #1486: ESM Error: Don't know what to do with INFO +Bug #1490: Arrows shot at PC can end up in inventory +Bug #1492: Monsters respawn on top of one another +Bug #1493: Dialogue box opens with follower NPC even if NPC is dead +Bug #1494: Paralysed cliffracers remain airbourne +Bug #1495: Dialogue box opens with follower NPC even the game is paused +Bug #1496: GUI messages are not cleared when loading another saved game +Bug #1499: Underwater sound sometimes plays when transitioning from interior. +Bug #1500: Targetted spells and water. +Bug #1502: Console error message on info refusal +Bug #1507: Bloodmoon MQ The Ritual of Beasts: Can't remove the arrow +Bug #1508: Bloodmoon: Fort Frostmoth, cant talk with Carnius Magius +Bug #1516: PositionCell doesn't move actors to current cell +Bug #1518: ForceGreeting broken for explicit references +Bug #1522: Crash after attempting to play non-music file +Bug #1523: World map empty after loading interior save +Bug #1524: Arrows in waiting/resting dialog act like minimum and maximum buttons +Bug #1525: Werewolf: Killed NPC's don't fill werewolfs hunger for blood +Bug #1527: Werewolf: Detect life detects wrong type of actor +Bug #1529: OpenMW crash during "the shrine of the dead" mission (tribunal) +Bug #1530: Selected text in the console has the same color as the background +Bug #1539: Barilzar's Mazed Band: Tribunal +Bug #1542: Looping taunts from NPC`s after death: Tribunal +Bug #1543: OpenCS crash when using drag&drop in script editor +Bug #1547: Bamz-Amschend: Centurion Archers combat problem +Bug #1548: The Missing Hand: Tribunal +Bug #1549: The Mad God: Tribunal, Dome of Serlyn +Bug #1557: A bounty is calculated from actual item cost +Bug #1562: Invisible terrain on top of Red Mountain +Bug #1564: Cave of the hidden music: Bloodmoon +Bug #1567: Editor: Deleting of referenceables does not work +Bug #1568: Picking up a stack of items and holding the enter key and moving your mouse around paints a bunch of garbage on screen. +Bug #1574: Solstheim: Drauger cant inflict damage on player +Bug #1578: Solstheim: Bonewolf running animation not working +Bug #1585: Particle effects on PC are stopped when paralyzed +Bug #1589: Tribunal: Crimson Plague quest does not update when Gedna Relvel is killed +Bug #1590: Failed to save game: compile error +Bug #1598: Segfault when making Drain/Fortify Skill spells +Bug #1599: Unable to switch to fullscreen +Bug #1613: Morrowind Rebirth duplicate objects / vanilla objects not removed +Feature #32: Periodic Cleanup/Refill +Feature #41: Precipitation and weather particles +Feature #568: Editor: Configuration setup +Feature #649: Editor: Threaded loading +Feature #930: Editor: Cell record saving +Feature #934: Editor: Body part table +Feature #935: Editor: Enchantment effect table +Feature #1162: Dialogue merging +Feature #1174: Saved Game: add missing creature state +Feature #1177: Saved Game: fog of war state +Feature #1312: Editor: Combat/Magic/Stealth values for creatures are not displayed +Feature #1314: Make NPCs and creatures fight each other +Feature #1315: Crime: Murder +Feature #1321: Sneak skill enhancements +Feature #1323: Handle restocking items +Feature #1332: Saved Game: levelled creatures +Feature #1347: modFactionReaction script instruction +Feature #1362: Animated main menu support +Feature #1433: Store walk/run toggle +Feature #1449: Use names instead of numbers for saved game files and folders +Feature #1453: Adding Delete button to the load menu +Feature #1460: Enable Journal screen while in dialogue +Feature #1480: Play Battle music when in combat +Feature #1501: Followers unable to fast travel with you +Feature #1520: Disposition and distance-based aggression/ShouldAttack +Feature #1595: Editor: Object rendering in cells +Task #940: Move license to locations where applicable +Task #1333: Remove cmake git tag reading +Task #1566: Editor: Object rendering refactoring + 0.30.0 Bug #416: Extreme shaking can occur during cell transitions while moving