1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-19 21:23:52 +00:00

Merge remote-tracking branch 'refs/remotes/master/master' into NonTableFields

Conflicts:
	apps/opencs/CMakeLists.txt
This commit is contained in:
Marek Kochanowicz 2014-07-25 11:13:52 +02:00
commit cdac934315
135 changed files with 2109 additions and 827 deletions

View file

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

View file

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

View file

@ -513,7 +513,7 @@ void Record<ESM::Cell>::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;
}

View file

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

View file

@ -977,13 +977,13 @@ namespace CSMWorld
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return record.get().mFactIndex;
return record.get().mFactionRank;
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mFactIndex = data.toInt();
record2.mFactionRank = data.toInt();
record.setModified (record2);
}

View file

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

View file

@ -3,6 +3,7 @@
#include <QEvent>
#include <QResizeEvent>
#include <QTimer>
#include <QShortcut>
#include <OgreRoot.h>
#include <OgreRenderWindow.h>
@ -11,7 +12,7 @@
#include <OgreSceneNode.h>
#include <OgreViewport.h>
#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"
"<ul><li>Cell specific ambient in interiors</li>"
"<li>Low ambient in exteriors</li>"
"<li>Strong directional light source/lir>"
"<li>This mode closely resembles day time in-game</li></ul>");
tool->addButton (":GMST.png", "night",
"Night"
"<ul><li>Cell specific ambient in interiors</li>"
"<li>Low ambient in exteriors</li>"
"<li>Weak directional light source</li>"
"<li>This mode closely resembles night time in-game</li></ul>");
tool->addButton (":Info.png", "bright",
"Bright"
"<ul><li>Maximum ambient</li>"
"<li>Strong directional light source</li></ul>");
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)

View file

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

View file

@ -7,8 +7,9 @@
#include <QtGui/qevent.h>
#include "../world/scenetoolmode.hpp"
#include <apps/opencs/model/world/universalid.hpp>
#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"
"<ul><li>Mouse-Look while holding the left button</li>"
"<li>WASD movement keys</li>"
"<li>Mouse wheel moves the camera forawrd/backward</li>"
"<li>Stafing (also vertically) by holding the left mouse button and control</li>"
"<li>Camera is held upright</li>"
"<li>Hold shift to speed up movement</li>"
"</ul>");
tool->addButton (":GMST.png", "free",
"Free Camera"
"<ul><li>Mouse-Look while holding the left button</li>"
"<li>Stafing (also vertically) via WASD or by holding the left mouse button and control</li>"
"<li>Mouse wheel moves the camera forawrd/backward</li>"
"<li>Roll camera with Q and E keys</li>"
"<li>Hold shift to speed up movement</li>"
"</ul>");
tool->addButton (":Info.png", "orbit",
"Orbiting Camera"
"<ul><li>Always facing the centre point</li>"
"<li>Rotate around the centre point via WASD or by moving the mouse while holding the left button</li>"
"<li>Mouse wheel moves camera away or towards centre point but can not pass through it</li>"
"<li>Roll camera with Q and E keys</li>"
"<li>Stafing (also vertically) by holding the left mouse button and control (includes relocation of the centre point)</li>"
"<li>Hold shift to speed up movement</li>"
"</ul>");
connect (tool, SIGNAL (modeChanged (const std::string&)),
this, SLOT (selectNavigationMode (const std::string&)));

View file

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

View file

@ -0,0 +1,83 @@
#include "pushbutton.hpp"
#include <QMouseEvent>
#include <QKeyEvent>
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 +=
"<p>(left click to change mode)";
break;
case Type_Mode:
tooltip +=
"<p>(left click to activate,"
"<br>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;
}

View file

@ -0,0 +1,55 @@
#ifndef CSV_WIDGET_PUSHBUTTON_H
#define CSV_WIDGET_PUSHBUTTON_H
#include <QPushButton>
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

View file

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

View file

@ -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 <QPushButton>
#include "pushbutton.hpp"
namespace CSVWorld
namespace CSVWidget
{
class SceneToolbar;
///< \brief Tool base class
class SceneTool : public QPushButton
class SceneTool : public PushButton
{
Q_OBJECT

View file

@ -0,0 +1,47 @@
#include "scenetoolbar.hpp"
#include <QVBoxLayout>
#include <QShortcut>
#include "scenetool.hpp"
void CSVWidget::SceneToolbar::focusInEvent (QFocusEvent *event)
{
QWidget::focusInEvent (event);
if (mLayout->count())
dynamic_cast<QWidgetItem&> (*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;
}

View file

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

View file

@ -0,0 +1,86 @@
#include "scenetoolmode.hpp"
#include <QHBoxLayout>
#include <QFrame>
#include <QSignalMapper>
#include "scenetoolbar.hpp"
#include "pushbutton.hpp"
void CSVWidget::SceneToolMode::adjustToolTip (const PushButton *activeMode)
{
QString toolTip = mToolTip;
toolTip += "<p>Currently selected: " + activeMode->getBaseToolTip();
toolTip += "<p>(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<PushButton *, std::string>::const_iterator iter =
mButtons.find (dynamic_cast<PushButton *> (sender()));
if (iter!=mButtons.end())
{
if (!iter->first->hasKeepOpen())
mPanel->hide();
for (std::map<PushButton *, std::string>::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);
}
}

View file

@ -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<QPushButton *, std::string> mButtons; // widget, id
std::map<PushButton *, std::string> 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:

View file

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

View file

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

View file

@ -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,14 +97,14 @@ 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.
@ -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);
}

View file

@ -4,7 +4,6 @@
#include <QHBoxLayout>
#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:

View file

@ -1,34 +0,0 @@
#include "scenetoolbar.hpp"
#include <QVBoxLayout>
#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;
}

View file

@ -1,57 +0,0 @@
#include "scenetoolmode.hpp"
#include <QHBoxLayout>
#include <QFrame>
#include <QSignalMapper>
#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<QPushButton *, std::string>::const_iterator iter =
mButtons.find (dynamic_cast<QPushButton *> (sender()));
if (iter!=mButtons.end())
{
mPanel->hide();
setIcon (iter->first->icon());
emit modeChanged (iter->second);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<ESM::Weapon>()->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

View file

@ -62,7 +62,7 @@ namespace MWClass
const DoorCustomData& customData = dynamic_cast<const DoorCustomData&>(*ptr.getRefData().getCustomData());
if (customData.mDoorState > 0)
{
MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState == 1 ? true : false);
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;

View file

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

View file

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

View file

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

View file

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

View file

@ -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<int>((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<ESM::Spell>::iterator it = store.get<ESM::Spell>().begin();
for (; it != store.get<ESM::Spell>().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(
round_ieee_754(
npcStats.getSkill(skillIndex).getBase()
+ 5
+ raceBonus
+ specBonus
+ static_cast<int>((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<ESM::Skill::Length; ++i)
skills[i] = npcStats.getSkill(i).getBase();
int attributes[ESM::Attribute::Length];
for (int i=0; i<ESM::Attribute::Length; ++i)
attributes[i] = npcStats.getAttribute(i).getBase();
std::vector<std::string> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race);
for (std::vector<std::string>::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)
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<MWWorld::Action>(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<MWWorld::Action>(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<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
// Can't talk to werewolfs
if(ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).isWerewolf())
return boost::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
}
MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -76,7 +76,17 @@ 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
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;
}

View file

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

View file

@ -20,6 +20,8 @@ namespace MWGui
virtual void exit();
virtual void resetReference();
void open(const MWWorld::Ptr& npc);
void onFrame ();

View file

@ -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<ESM::Container>* ref = mPtr.get<ESM::Container>();
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();

View file

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

View file

@ -273,7 +273,12 @@ namespace MWGui
{
if ((!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice())
&& !mGoodbye)
return;
{
// 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();
}

View file

@ -260,14 +260,20 @@ 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<int, int> additional;
additional[39] = 0x2019; // apostrophe
additional[45] = 0x2013; // dash
if (additional.find(i) != additional.end() && mEncoding == ToUTF8::CP437)
if (mEncoding == ToUTF8::CP437)
{
std::multimap<int, int> 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<int, int>::iterator it = additional.begin(); it != additional.end(); ++it)
{
if (it->first != i)
continue;
MyGUI::xml::ElementPtr code = codes->createChild("Code");
code->addAttribute("index", additional[i]);
code->addAttribute("index", it->second);
code->addAttribute("coord", MyGUI::utility::toString(x1) + " "
+ MyGUI::utility::toString(y1) + " "
+ MyGUI::utility::toString(w) + " "
@ -276,6 +282,7 @@ namespace MWGui
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
if (i == 124)

View file

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

View file

@ -104,7 +104,7 @@ namespace MWGui
SpellIcons* mSpellIcons;
MWWorld::Ptr mEnemy;
int mEnemyActorId;
float mEnemyHealthTimer;
bool mIsDrowning;

View file

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

View file

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

View file

@ -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<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(curX,250,16,16), MyGUI::Align::Default);
MyGUI::ImageBox* image = mCoinBox->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(0,0,16,16), MyGUI::Align::Default);
image->setImageTexture ("icons\\tx_goldicon.dds");
curX += 24+2;
mCoins.push_back(image);
}
@ -82,14 +81,15 @@ namespace MWGui
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; i<mCoinCount; ++i)
{
MyGUI::ImageBox* image = mCoins[i];
image->detachFromWidget();
image->attachToWidget(mCoinBox);
image->setCoord(MyGUI::IntCoord(curX,0,16,16));
curX += 24+2;
curX += 16+coinSpacing;
}
}
@ -121,32 +121,28 @@ namespace MWGui
MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player);
MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player);
mSpentAttributes.clear();
resetCoins();
setAttributeValues();
const ESM::NPC *playerData = player.get<ESM::NPC>()->mBase;
// set class image
const ESM::Class *cls =
world->getStore().get<ESM::Class>().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<ESM::Class>().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<ESM::Class> it = world->getStore().get<ESM::Class>().begin();
for(; it != world->getStore().get<ESM::Class>().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<ESM::Class>().end())
it = world->getStore().get<ESM::Class>().begin();
if (it != world->getStore().get<ESM::Class>().end())
cls = &*it;
}
else
mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds");
int level = creatureStats.getLevel ()+1;
@ -160,12 +156,40 @@ namespace MWGui
mLevelDescription->setCaption (levelupdescription);
unsigned int availableAttributes = 0;
for (int i = 0; i < 8; ++i)
{
MyGUI::TextBox* text = mAttributeMultipliers[i];
if (pcStats.getAttribute(i).getBase() < 100)
{
mAttributes[i]->setEnabled(true);
availableAttributes++;
int mult = pcStats.getLevelupAttributeMultiplier (i);
text->setCaption(mult <= 1 ? "" : "x" + boost::lexical_cast<std::string>(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();
}
@ -175,12 +199,12 @@ namespace MWGui
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player);
if (mSpentAttributes.size() < 3)
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]));
@ -206,8 +230,8 @@ namespace MWGui
mSpentAttributes.erase(found);
else
{
if (mSpentAttributes.size() == 3)
mSpentAttributes[2] = attribute;
if (mSpentAttributes.size() == mCoinCount)
mSpentAttributes[mCoinCount - 1] = attribute;
else
mSpentAttributes.push_back(attribute);
}

View file

@ -28,6 +28,9 @@ namespace MWGui
std::vector<int> mSpentAttributes;
unsigned int mCoinCount;
static const unsigned int sMaxCoins;
void onOkButtonClicked(MyGUI::Widget* sender);
void onAttributeClicked(MyGUI::Widget* sender);

View file

@ -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<MWWorld::Ptr>();
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<int>(sender->getUserString("Price"));
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
startRepair(mActor);

View file

@ -165,6 +165,8 @@ void Recharge::onItemClicked(MyGUI::Widget *sender)
item.getCellRef().setEnchantmentCharge(
std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast<float>(enchantment->mData.mCharge)));
player.getClass().getContainerStore(player).restack(item);
player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0);
}

View file

@ -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<std::string>(int(Settings::Manager::getInt("field of view", "General"))) + ")");
MyGUI::TextBox* diffText;
getWidget(diffText, "DifficultyText");
diffText->setCaptionWithReplacing("#{sDifficulty} (" + boost::lexical_cast<std::string>(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<std::string>(int(value)) + ")");
}
if (scroller == mDifficultySlider)
{
MyGUI::TextBox* diffText;
getWidget(diffText, "DifficultyText");
diffText->setCaptionWithReplacing("#{sDifficulty} (" + boost::lexical_cast<std::string>(int(value)) + ")");
}
}
else
{

View file

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

View file

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

View file

@ -547,6 +547,17 @@ namespace MWGui
return " (" + boost::lexical_cast<std::string>(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;

View file

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

View file

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

View file

@ -37,6 +37,7 @@ namespace MWGui
virtual void exit();
virtual void resetReference();
private:
ItemView* mItemView;

View file

@ -268,7 +268,7 @@ namespace MWGui
mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager);
trackWindow(mCompanionWindow, "companion");
mInputBlocker = mGui->createWidget<MyGUI::Widget>("",0,0,w,h,MyGUI::Align::Default,"Windows");
mInputBlocker = mGui->createWidget<MyGUI::Widget>("",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();

View file

@ -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,10 +699,12 @@ namespace MWInput
}
void InputManager::quickLoad() {
if (!MyGUI::InputManager::getInstance().isModalAny())
MWBase::Environment::get().getStateManager()->quickLoad();
}
void InputManager::quickSave() {
if (!MyGUI::InputManager::getInstance().isModalAny())
MWBase::Environment::get().getStateManager()->quickSave();
}
void InputManager::toggleSpell()

View file

@ -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<float> 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<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
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<float> 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<ESM::GameSetting>().find("iCrimeThreshold")->getInt());
int cutoff = esmStore.get<ESM::GameSetting>().find("iCrimeThreshold")->getInt();
// Force dialogue on sight if bounty is greater than the cutoff
// In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty)
if ( player.getClass().getNpcStats(player).getBounty() >= cutoff
@ -848,6 +868,10 @@ namespace MWMechanics
&& MWBase::Environment::get().getWorld()->getLOS(ptr, player)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr))
{
static int iCrimeThresholdMultiplier = esmStore.get<ESM::GameSetting>().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<float> 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<float> 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,13 +1324,9 @@ 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<MWMechanics::AiCombat*>(stats.getAiSequence().getActivePackage());
if(package->getTarget() == actor)
if (!stats.isDead() && stats.getAiSequence().isInCombat(actor))
list.push_front(*iter);
}
}
return list;
}

View file

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

View file

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

View file

@ -72,6 +72,30 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const
return true;
}
bool AiSequence::isInCombat() const
{
for(std::list<AiPackage*>::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<AiPackage*>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
{
if ((*it)->getTypeId() == AiPackage::TypeIdCombat)
{
const AiCombat *combat = static_cast<const AiCombat *>(*it);
if (combat->getTarget() == actor)
return true;
}
}
return false;
}
bool AiSequence::canAddTarget(const ESM::Position& actorPos, float distToTarget) const
{
bool firstCombatFound = false;

View file

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

View file

@ -0,0 +1,232 @@
#include "autocalcspell.hpp"
#include <climits>
#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<std::string> autoCalcNpcSpells(const int *actorSkills, const int *actorAttributes, const ESM::Race* race)
{
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
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<int, SchoolCaps> 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<std::string> selectedSpells;
const MWWorld::Store<ESM::Spell> &spells =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>();
// 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<ESM::Spell>::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<std::string>::iterator found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell);
if (found != selectedSpells.end())
selectedSpells.erase(found);
cap.mMinCost = INT_MAX;
for (std::vector<std::string>::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<ESM::ENAMstruct>& effects = spell->mEffects.mList;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
{
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectIt->mEffectID);
static const int iAutoSpellAttSkillMin = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().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<int, ESM::Skill::SkillEnum> 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<ESM::ENAMstruct>::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<ESM::MagicEffect>().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<ESM::GameSetting>().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;
}
}

View file

@ -0,0 +1,31 @@
#ifndef OPENMW_AUTOCALCSPELL_H
#define OPENMW_AUTOCALCSPELL_H
#include <cfloat>
#include <set>
#include <components/esm/loadspel.hpp>
#include <components/esm/loadskil.hpp>
#include <components/esm/loadrace.hpp>
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<std::string> 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

View file

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

View file

@ -147,7 +147,7 @@ class CharacterController
CharacterState mMovementState;
std::string mCurrentMovement;
float mMovementSpeed;
float mMovementAnimVelocity;
bool mMovementAnimationControlled;
CharacterState mDeathState;
std::string mCurrentDeath;

View file

@ -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<ESM::Weapon>()->mBase->mData.mFlags & ESM::Weapon::Silver
|| weapon.get<ESM::Weapon>()->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<float> (fatigueCurrent)/fatigueMax);
saveTerm *= 1.25f * normalisedFatigue;
float roll = std::rand()/ (static_cast<double> (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<ESM::GameSetting>().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<float> health = attackerStats.getHealth();
health.setCurrent(health.getCurrent() - x);
attackerStats.setHealth(health);
}
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,38 @@
#include "difficultyscaling.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp"
#include <components/settings/settings.hpp>
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<ESM::GameSetting>().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;
}

View file

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

View file

@ -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,17 +29,41 @@ namespace MWMechanics
for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(it->first);
if (spell->mData.mType == ESM::Spell::ST_Disease
|| spell->mData.mType == ESM::Spell::ST_Blight)
if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId))
continue;
bool hasCorprusEffect = false;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt)
{
float roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
if (roll < fDiseaseXferChance)
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<double> (RAND_MAX) + 1) * 10000; // [0, 9999]
if (roll < x)
{
// 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<ESM::GameSetting>().find(msg)->getString();
if (msg.find("%s") != std::string::npos)
@ -47,8 +73,6 @@ namespace MWMechanics
}
}
}
}
}
#endif

View file

@ -19,6 +19,7 @@
#include <OgreSceneNode.h>
#include "spellcasting.hpp"
#include "autocalcspell.hpp"
namespace
{
@ -33,10 +34,19 @@ namespace
if (!faction.empty() && ptr.getClass().isNpc())
{
const std::map<std::string, int>& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks();
if (factions.find(Misc::StringUtils::lowerCase(faction)) == factions.end())
std::map<std::string, int>::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<ESM::Spell>::iterator it = store.get<ESM::Spell>().begin();
for (; it != store.get<ESM::Spell>().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<ESM::GameSetting>().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<std::string> selectedSpells;
const ESM::Race* race = NULL;
if (mRaceSelected)
race = esmStore.get<ESM::Race>().find(player->mRace);
int skills[ESM::Skill::Length];
for (int i=0; i<ESM::Skill::Length; ++i)
skills[i] = npcStats.getSkill(i).getBase();
int attributes[ESM::Attribute::Length];
for (int i=0; i<ESM::Attribute::Length; ++i)
attributes[i] = npcStats.getAttribute(i).getBase();
const MWWorld::Store<ESM::Spell> &spells =
esmStore.get<ESM::Spell>();
for (MWWorld::Store<ESM::Spell>::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<ESM::GameSetting>().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<std::string>::iterator it = std::find(selectedSpells.begin(), selectedSpells.end(), weakestSpell->mId);
if (it != selectedSpells.end())
selectedSpells.erase(it);
minCost = INT_MAX;
for (std::vector<std::string>::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt)
{
const ESM::Spell* testSpell = esmStore.get<ESM::Spell>().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<ESM::GameSetting>().find("iAutoPCSpellMax")->getInt();
if (selectedSpells.size() == iAutoPCSpellMax)
reachedLimit = true;
}
}
for (std::vector<std::string>::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)

View file

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

View file

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

View file

@ -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<ESM::GameSetting>().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,20 +496,26 @@ 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)
{
if (target.getCellRef().getLockLevel() <= magnitude)
{
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);

View file

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

View file

@ -149,6 +149,8 @@ namespace MWRender
mViewModeToggleQueued = true;
return;
}
else
mViewModeToggleQueued = false;
mFirstPersonView = !mFirstPersonView;
processViewChange();

View file

@ -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<uint32> buffer;
// initialize to (0, 0, 0, 1)
buffer.resize(sFogOfWarResolution*sFogOfWarResolution, 0xFF000000);
mBuffers[resourceName] = buffer;
}
std::vector<uint32>& buffer = mBuffers[resourceName];
Ogre::Texture* tex = dynamic_cast<Ogre::Texture*>(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)

View file

@ -5,6 +5,7 @@
#include <OgreAxisAlignedBox.h>
#include <OgreColourValue.h>
#include <OgreResource.h>
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)
*/

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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