Merge remote-tracking branch 'upstream/master' into wizard

loadfix
pvdk 11 years ago
commit a2c129f655

@ -41,4 +41,4 @@ notifications:
- "chat.freenode.net#openmw" - "chat.freenode.net#openmw"
on_success: change on_success: change
on_failure: always on_failure: always
use_notice: true

@ -24,7 +24,7 @@ opencs_units (model/world
opencs_units_noqt (model/world opencs_units_noqt (model/world
universalid record commands columnbase scriptcontext cell refidcollection universalid record commands columnbase scriptcontext cell refidcollection
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection
) )
opencs_hdrs_noqt (model/world opencs_hdrs_noqt (model/world
@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc
opencs_units (view/world opencs_units (view/world
table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator
cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool
scenetoolmode infocreator scriptedit dialoguesubview previewsubview scenetoolmode infocreator scriptedit dialoguesubview previewsubview regionmap
) )
opencs_units (view/render opencs_units (view/render

@ -9,6 +9,9 @@
namespace CSMWorld namespace CSMWorld
{ {
/// \brief Wrapper for Cell record /// \brief Wrapper for Cell record
///
/// \attention The mData.mX and mData.mY fields of the ESM::Cell struct are not used.
/// Exterior cell coordinates are encoded in the cell ID.
struct Cell : public ESM::Cell struct Cell : public ESM::Cell
{ {
std::string mId; std::string mId;

@ -0,0 +1,60 @@
#include "cellcoordinates.hpp"
#include <ostream>
#include <sstream>
CSMWorld::CellCoordinates::CellCoordinates() : mX (0), mY (0) {}
CSMWorld::CellCoordinates::CellCoordinates (int x, int y) : mX (x), mY (y) {}
int CSMWorld::CellCoordinates::getX() const
{
return mX;
}
int CSMWorld::CellCoordinates::getY() const
{
return mY;
}
CSMWorld::CellCoordinates CSMWorld::CellCoordinates::move (int x, int y) const
{
return CellCoordinates (mX + x, mY + y);
}
std::string CSMWorld::CellCoordinates::getId (const std::string& worldspace) const
{
// we ignore the worldspace for now, since there is only one (will change in 1.1)
std::ostringstream stream;
stream << "#" << mX << " " << mY;
return stream.str();
}
bool CSMWorld::operator== (const CellCoordinates& left, const CellCoordinates& right)
{
return left.getX()==right.getX() && left.getY()==right.getY();
}
bool CSMWorld::operator!= (const CellCoordinates& left, const CellCoordinates& right)
{
return !(left==right);
}
bool CSMWorld::operator< (const CellCoordinates& left, const CellCoordinates& right)
{
if (left.getX()<right.getX())
return true;
if (left.getX()>right.getX())
return false;
return left.getY()<right.getY();
}
std::ostream& CSMWorld::operator<< (std::ostream& stream, const CellCoordinates& coordiantes)
{
return stream << coordiantes.getX() << ", " << coordiantes.getY();
}

@ -0,0 +1,42 @@
#ifndef CSM_WOLRD_CELLCOORDINATES_H
#define CSM_WOLRD_CELLCOORDINATES_H
#include <iosfwd>
#include <string>
#include <QMetaType>
namespace CSMWorld
{
class CellCoordinates
{
int mX;
int mY;
public:
CellCoordinates();
CellCoordinates (int x, int y);
int getX() const;
int getY() const;
CellCoordinates move (int x, int y) const;
///< Return a copy of *this, moved by the given offset.
std::string getId (const std::string& worldspace) const;
///< Return the ID for the cell at these coordinates.
};
bool operator== (const CellCoordinates& left, const CellCoordinates& right);
bool operator!= (const CellCoordinates& left, const CellCoordinates& right);
bool operator< (const CellCoordinates& left, const CellCoordinates& right);
std::ostream& operator<< (std::ostream& stream, const CellCoordinates& coordiantes);
}
Q_DECLARE_METATYPE (CSMWorld::CellCoordinates)
#endif

@ -0,0 +1,83 @@
#include "cellselection.hpp"
#include <cmath>
#include <stdexcept>
#include <limits>
CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::begin() const
{
return mCells.begin();
}
CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::end() const
{
return mCells.end();
}
bool CSMWorld::CellSelection::add (const CellCoordinates& coordinates)
{
return mCells.insert (coordinates).second;
}
void CSMWorld::CellSelection::remove (const CellCoordinates& coordinates)
{
mCells.erase (coordinates);
}
bool CSMWorld::CellSelection::has (const CellCoordinates& coordinates) const
{
return mCells.find (coordinates)!=end();
}
int CSMWorld::CellSelection::getSize() const
{
return mCells.size();
}
CSMWorld::CellCoordinates CSMWorld::CellSelection::getCentre() const
{
if (mCells.empty())
throw std::logic_error ("call of getCentre on empty cell selection");
double x = 0;
double y = 0;
for (Iterator iter = begin(); iter!=end(); ++iter)
{
x += iter->getX();
y += iter->getY();
}
x /= mCells.size();
y /= mCells.size();
Iterator closest = begin();
double distance = std::numeric_limits<double>::max();
for (Iterator iter (begin()); iter!=end(); ++iter)
{
double deltaX = x - iter->getX();
double deltaY = y - iter->getY();
double delta = std::sqrt (deltaX * deltaX + deltaY * deltaY);
if (delta<distance)
{
distance = delta;
closest = iter;
}
}
return *closest;
}
void CSMWorld::CellSelection::move (int x, int y)
{
Container moved;
for (Iterator iter = begin(); iter!=end(); ++iter)
moved.insert (iter->move (x, y));
mCells.swap (moved);
}

@ -0,0 +1,57 @@
#ifndef CSM_WOLRD_CELLSELECTION_H
#define CSM_WOLRD_CELLSELECTION_H
#include <set>
#include <QMetaType>
#include "cellcoordinates.hpp"
namespace CSMWorld
{
/// \brief Selection of cells in a paged worldspace
///
/// \note The CellSelection does not specify the worldspace it applies to.
class CellSelection
{
public:
typedef std::set<CellCoordinates> Container;
typedef Container::const_iterator Iterator;
private:
Container mCells;
public:
Iterator begin() const;
Iterator end() const;
bool add (const CellCoordinates& coordinates);
///< Ignored if the cell specified by \a coordinates is already part of the selection.
///
/// \return Was a cell added to the collection?
void remove (const CellCoordinates& coordinates);
///< ignored if the cell specified by \a coordinates is not part of the selection.
bool has (const CellCoordinates& coordinates) const;
///< \return Is the cell specified by \a coordinates part of the selection?
int getSize() const;
///< Return number of cells.
CellCoordinates getCentre() const;
///< Return the selected cell that is closest to the geometric centre of the selection.
///
/// \attention This function must not be called on selections that are empty.
void move (int x, int y);
};
}
Q_DECLARE_METATYPE (CSMWorld::CellSelection)
#endif

@ -785,7 +785,7 @@ namespace CSMWorld
template<typename ESXRecordT> template<typename ESXRecordT>
struct RegionColumn : public Column<ESXRecordT> struct RegionColumn : public Column<ESXRecordT>
{ {
RegionColumn() : Column<ESXRecordT> (Columns::ColumnId_Region, ColumnBase::Display_String) {} RegionColumn() : Column<ESXRecordT> (Columns::ColumnId_Region, ColumnBase::Display_Region) {}
virtual QVariant get (const Record<ESXRecordT>& record) const virtual QVariant get (const Record<ESXRecordT>& record) const
{ {

@ -1,6 +1,7 @@
#include "regionmap.hpp" #include "regionmap.hpp"
#include <cmath>
#include <algorithm> #include <algorithm>
#include <QBrush> #include <QBrush>
@ -25,17 +26,28 @@ CSMWorld::RegionMap::CellDescription::CellDescription (const Record<Cell>& cell)
mName = cell2.mName; mName = cell2.mName;
} }
CSMWorld::RegionMap::CellIndex CSMWorld::RegionMap::getIndex (const QModelIndex& index) const CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex (const QModelIndex& index) const
{ {
return CellIndex (index.column()+mMin.first, return mMin.move (index.column(), mMax.getY()-mMin.getY() - index.row()-1);
(mMax.second-mMin.second - index.row()-1)+mMin.second);
} }
QModelIndex CSMWorld::RegionMap::getIndex (const CellIndex& index) const QModelIndex CSMWorld::RegionMap::getIndex (const CellCoordinates& index) const
{ {
// I hate you, Qt API naming scheme! // I hate you, Qt API naming scheme!
return QAbstractTableModel::index (mMax.second-mMin.second - (index.second-mMin.second)-1, return QAbstractTableModel::index (mMax.getY()-mMin.getY() - (index.getY()-mMin.getY())-1,
index.first-mMin.first); index.getX()-mMin.getX());
}
CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex (const Cell& cell) const
{
std::istringstream stream (cell.mId);
char ignore;
int x = 0;
int y = 0;
stream >> ignore >> x >> y;
return CellCoordinates (x, y);
} }
void CSMWorld::RegionMap::buildRegions() void CSMWorld::RegionMap::buildRegions()
@ -70,21 +82,21 @@ void CSMWorld::RegionMap::buildMap()
{ {
CellDescription description (cell); CellDescription description (cell);
CellIndex index (cell2.mData.mX, cell2.mData.mY); CellCoordinates index = getIndex (cell2);
mMap.insert (std::make_pair (index, description)); mMap.insert (std::make_pair (index, description));
} }
} }
std::pair<CellIndex, CellIndex> mapSize = getSize(); std::pair<CellCoordinates, CellCoordinates> mapSize = getSize();
mMin = mapSize.first; mMin = mapSize.first;
mMax = mapSize.second; mMax = mapSize.second;
} }
void CSMWorld::RegionMap::addCell (const CellIndex& index, const CellDescription& description) void CSMWorld::RegionMap::addCell (const CellCoordinates& index, const CellDescription& description)
{ {
std::map<CellIndex, CellDescription>::iterator cell = mMap.find (index); std::map<CellCoordinates, CellDescription>::iterator cell = mMap.find (index);
if (cell!=mMap.end()) if (cell!=mMap.end())
{ {
@ -114,7 +126,7 @@ void CSMWorld::RegionMap::addCells (int start, int end)
if (cell2.isExterior()) if (cell2.isExterior())
{ {
CellIndex index (cell2.mData.mX, cell2.mData.mY); CellCoordinates index = getIndex (cell2);
CellDescription description (cell); CellDescription description (cell);
@ -123,9 +135,9 @@ void CSMWorld::RegionMap::addCells (int start, int end)
} }
} }
void CSMWorld::RegionMap::removeCell (const CellIndex& index) void CSMWorld::RegionMap::removeCell (const CellCoordinates& index)
{ {
std::map<CellIndex, CellDescription>::iterator cell = mMap.find (index); std::map<CellCoordinates, CellDescription>::iterator cell = mMap.find (index);
if (cell!=mMap.end()) if (cell!=mMap.end())
{ {
@ -160,7 +172,7 @@ void CSMWorld::RegionMap::updateRegions (const std::vector<std::string>& regions
std::for_each (regions2.begin(), regions2.end(), &Misc::StringUtils::lowerCase); std::for_each (regions2.begin(), regions2.end(), &Misc::StringUtils::lowerCase);
std::sort (regions2.begin(), regions2.end()); std::sort (regions2.begin(), regions2.end());
for (std::map<CellIndex, CellDescription>::const_iterator iter (mMap.begin()); for (std::map<CellCoordinates, CellDescription>::const_iterator iter (mMap.begin());
iter!=mMap.end(); ++iter) iter!=mMap.end(); ++iter)
{ {
if (!iter->second.mRegion.empty() && if (!iter->second.mRegion.empty() &&
@ -176,90 +188,57 @@ void CSMWorld::RegionMap::updateRegions (const std::vector<std::string>& regions
void CSMWorld::RegionMap::updateSize() void CSMWorld::RegionMap::updateSize()
{ {
std::pair<CellIndex, CellIndex> size = getSize(); std::pair<CellCoordinates, CellCoordinates> size = getSize();
if (int diff = size.first.getX() - mMin.getX())
{ {
int diff = size.first.first - mMin.first; beginInsertColumns (QModelIndex(), 0, std::abs (diff)-1);
mMin = CellCoordinates (size.first.getX(), mMin.getY());
if (diff<0) endInsertColumns();
{
beginInsertColumns (QModelIndex(), 0, -diff-1);
mMin.first = size.first.first;
endInsertColumns();
}
else if (diff>0)
{
beginRemoveColumns (QModelIndex(), 0, diff-1);
mMin.first = size.first.first;
endRemoveColumns();
}
} }
if (int diff = size.first.getY() - mMin.getY())
{ {
int diff = size.first.second - mMin.second; beginInsertRows (QModelIndex(), 0, std::abs (diff)-1);
mMin = CellCoordinates (mMin.getX(), size.first.getY());
if (diff<0) endInsertRows();
{
beginInsertRows (QModelIndex(), 0, -diff-1);
mMin.second = size.first.second;
endInsertRows();
}
else if (diff>0)
{
beginRemoveRows (QModelIndex(), 0, diff-1);
mMin.second = size.first.second;
endRemoveRows();
}
} }
if (int diff = size.second.getX() - mMax.getX())
{ {
int diff = size.second.first - mMax.first; int columns = columnCount();
if (diff>0) if (diff>0)
{
int columns = columnCount();
beginInsertColumns (QModelIndex(), columns, columns+diff-1); beginInsertColumns (QModelIndex(), columns, columns+diff-1);
mMax.first = size.second.first; else
endInsertColumns();
}
else if (diff<0)
{
int columns = columnCount();
beginRemoveColumns (QModelIndex(), columns+diff, columns-1); beginRemoveColumns (QModelIndex(), columns+diff, columns-1);
mMax.first = size.second.first;
endRemoveColumns(); mMax = CellCoordinates (size.second.getX(), mMax.getY());
} endInsertColumns();
} }
if (int diff = size.second.getY() - mMax.getY())
{ {
int diff = size.second.second - mMax.second; int rows = rowCount();
if (diff>0) if (diff>0)
{
int rows = rowCount();
beginInsertRows (QModelIndex(), rows, rows+diff-1); beginInsertRows (QModelIndex(), rows, rows+diff-1);
mMax.second = size.second.second; else
endInsertRows();
}
else if (diff<0)
{
int rows = rowCount();
beginRemoveRows (QModelIndex(), rows+diff, rows-1); beginRemoveRows (QModelIndex(), rows+diff, rows-1);
mMax.second = size.second.second;
endRemoveRows(); mMax = CellCoordinates (mMax.getX(), size.second.getY());
} endInsertRows();
} }
} }
std::pair<CSMWorld::RegionMap::CellIndex, CSMWorld::RegionMap::CellIndex> CSMWorld::RegionMap::getSize() std::pair<CSMWorld::CellCoordinates, CSMWorld::CellCoordinates> CSMWorld::RegionMap::getSize() const
const
{ {
const IdCollection<Cell>& cells = mData.getCells(); const IdCollection<Cell>& cells = mData.getCells();
int size = cells.getSize(); int size = cells.getSize();
CellIndex min (0, 0); CellCoordinates min (0, 0);
CellIndex max (0, 0); CellCoordinates max (0, 0);
for (int i=0; i<size; ++i) for (int i=0; i<size; ++i)
{ {
@ -269,24 +248,24 @@ std::pair<CSMWorld::RegionMap::CellIndex, CSMWorld::RegionMap::CellIndex> CSMWor
if (cell2.isExterior()) if (cell2.isExterior())
{ {
CellIndex index (cell2.mData.mX, cell2.mData.mY); CellCoordinates index = getIndex (cell2);
if (min==max) if (min==max)
{ {
min = index; min = index;
max = std::make_pair (min.first+1, min.second+1); max = min.move (1, 1);
} }
else else
{ {
if (index.first<min.first) if (index.getX()<min.getX())
min.first = index.first; min = CellCoordinates (index.getX(), min.getY());
else if (index.first>=max.first) else if (index.getX()>=max.getX())
max.first = index.first + 1; max = CellCoordinates (index.getX()+1, max.getY());
if (index.second<min.second) if (index.getY()<min.getY())
min.second = index.second; min = CellCoordinates (min.getX(), index.getY());
else if (index.second>=max.second) else if (index.getY()>=max.getY())
max.second = index.second + 1; max = CellCoordinates (max.getX(), index.getY() + 1);
} }
} }
} }
@ -323,7 +302,7 @@ int CSMWorld::RegionMap::rowCount (const QModelIndex& parent) const
if (parent.isValid()) if (parent.isValid())
return 0; return 0;
return mMax.second-mMin.second; return mMax.getY()-mMin.getY();
} }
int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const
@ -331,7 +310,7 @@ int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const
if (parent.isValid()) if (parent.isValid())
return 0; return 0;
return mMax.first-mMin.first; return mMax.getX()-mMin.getX();
} }
QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const
@ -343,7 +322,7 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const
{ {
/// \todo GUI class in non-GUI code. Needs to be addressed eventually. /// \todo GUI class in non-GUI code. Needs to be addressed eventually.
std::map<CellIndex, CellDescription>::const_iterator cell = std::map<CellCoordinates, CellDescription>::const_iterator cell =
mMap.find (getIndex (index)); mMap.find (getIndex (index));
if (cell!=mMap.end()) if (cell!=mMap.end())
@ -370,13 +349,13 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const
if (role==Qt::ToolTipRole) if (role==Qt::ToolTipRole)
{ {
CellIndex cellIndex = getIndex (index); CellCoordinates cellIndex = getIndex (index);
std::ostringstream stream; std::ostringstream stream;
stream << cellIndex.first << ", " << cellIndex.second; stream << cellIndex;
std::map<CellIndex, CellDescription>::const_iterator cell = std::map<CellCoordinates, CellDescription>::const_iterator cell =
mMap.find (cellIndex); mMap.find (cellIndex);
if (cell!=mMap.end()) if (cell!=mMap.end())
@ -406,15 +385,33 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const
return QString::fromUtf8 (stream.str().c_str()); return QString::fromUtf8 (stream.str().c_str());
} }
if (role==Role_Region)
{
CellCoordinates cellIndex = getIndex (index);
std::map<CellCoordinates, CellDescription>::const_iterator cell =
mMap.find (cellIndex);
if (cell!=mMap.end() && !cell->second.mRegion.empty())
return QString::fromUtf8 (Misc::StringUtils::lowerCase (cell->second.mRegion).c_str());
}
if (role==Role_CellId)
{
CellCoordinates cellIndex = getIndex (index);
std::ostringstream stream;
stream << "#" << cellIndex.getX() << " " << cellIndex.getY();
return QString::fromUtf8 (stream.str().c_str());
}
return QVariant(); return QVariant();
} }
Qt::ItemFlags CSMWorld::RegionMap::flags (const QModelIndex& index) const Qt::ItemFlags CSMWorld::RegionMap::flags (const QModelIndex& index) const
{ {
if (mMap.find (getIndex (index))!=mMap.end()) return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
return 0;
} }
void CSMWorld::RegionMap::regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end) void CSMWorld::RegionMap::regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end)
@ -491,11 +488,7 @@ void CSMWorld::RegionMap::cellsAboutToBeRemoved (const QModelIndex& parent, int
const Cell& cell2 = cell.get(); const Cell& cell2 = cell.get();
if (cell2.isExterior()) if (cell2.isExterior())
{ removeCell (getIndex (cell2));
CellIndex index (cell2.mData.mX, cell2.mData.mY);
removeCell (index);
}
} }
} }

@ -9,6 +9,7 @@
#include "record.hpp" #include "record.hpp"
#include "cell.hpp" #include "cell.hpp"
#include "cellcoordinates.hpp"
namespace CSMWorld namespace CSMWorld
{ {
@ -23,7 +24,11 @@ namespace CSMWorld
public: public:
typedef std::pair<int, int> CellIndex; enum Role
{
Role_Region = Qt::UserRole,
Role_CellId = Qt::UserRole+1
};
private: private:
@ -39,27 +44,29 @@ namespace CSMWorld
}; };
Data& mData; Data& mData;
std::map<CellIndex, CellDescription> mMap; std::map<CellCoordinates, CellDescription> mMap;
CellIndex mMin; ///< inclusive CellCoordinates mMin; ///< inclusive
CellIndex mMax; ///< exclusive CellCoordinates mMax; ///< exclusive
std::map<std::string, unsigned int> mColours; ///< region ID, colour (RGBA) std::map<std::string, unsigned int> mColours; ///< region ID, colour (RGBA)
CellIndex getIndex (const QModelIndex& index) const; CellCoordinates getIndex (const QModelIndex& index) const;
///< Translates a Qt model index into a cell index (which can contain negative components) ///< Translates a Qt model index into a cell index (which can contain negative components)
QModelIndex getIndex (const CellIndex& index) const; QModelIndex getIndex (const CellCoordinates& index) const;
CellCoordinates getIndex (const Cell& cell) const;
void buildRegions(); void buildRegions();
void buildMap(); void buildMap();
void addCell (const CellIndex& index, const CellDescription& description); void addCell (const CellCoordinates& index, const CellDescription& description);
///< May be called on a cell that is already in the map (in which case an update is ///< May be called on a cell that is already in the map (in which case an update is
// performed) // performed)
void addCells (int start, int end); void addCells (int start, int end);
void removeCell (const CellIndex& index); void removeCell (const CellCoordinates& index);
///< May be called on a cell that is not in the map (in which case the call is ignored) ///< May be called on a cell that is not in the map (in which case the call is ignored)
void addRegion (const std::string& region, unsigned int colour); void addRegion (const std::string& region, unsigned int colour);
@ -78,7 +85,7 @@ namespace CSMWorld
void updateSize(); void updateSize();
std::pair<CellIndex, CellIndex> getSize() const; std::pair<CellCoordinates, CellCoordinates> getSize() const;
public: public:
@ -89,6 +96,8 @@ namespace CSMWorld
virtual int columnCount (const QModelIndex& parent = QModelIndex()) const; virtual int columnCount (const QModelIndex& parent = QModelIndex()) const;
virtual QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const; virtual QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const;
///< \note Calling this function with role==Role_CellId may return the ID of a cell
/// that does not exist.
virtual Qt::ItemFlags flags (const QModelIndex& index) const; virtual Qt::ItemFlags flags (const QModelIndex& index) const;

@ -193,11 +193,4 @@ std::string CSVFilter::EditWidget::generateFilter (std::pair< std::string, std::
} }
return ss.str(); return ss.str();
} }
void CSVFilter::EditWidget::useFilterRequest (const std::string& idOfFilter)
{
clear();
insert(QString::fromUtf8(idOfFilter.c_str()));
}

@ -30,6 +30,9 @@ namespace CSVFilter
EditWidget (CSMWorld::Data& data, QWidget *parent = 0); EditWidget (CSMWorld::Data& data, QWidget *parent = 0);
void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
Qt::DropAction action);
signals: signals:
void filterChanged (boost::shared_ptr<CSMFilter::Node> filter); void filterChanged (boost::shared_ptr<CSMFilter::Node> filter);
@ -47,10 +50,7 @@ namespace CSVFilter
void filterRowsInserted (const QModelIndex& parent, int start, int end); void filterRowsInserted (const QModelIndex& parent, int start, int end);
void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
Qt::DropAction action);
void useFilterRequest(const std::string& idOfFilter);
}; };
} }

@ -15,23 +15,24 @@ CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent)
layout->setContentsMargins (0, 0, 0, 0); layout->setContentsMargins (0, 0, 0, 0);
RecordFilterBox *recordFilterBox = new RecordFilterBox (data, this); mRecordFilterBox = new RecordFilterBox (data, this);
layout->addWidget (recordFilterBox); layout->addWidget (mRecordFilterBox);
setLayout (layout); setLayout (layout);
connect (recordFilterBox, connect (mRecordFilterBox,
SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)), SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)),
this, SIGNAL (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>))); this, SIGNAL (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>)));
connect(this, SIGNAL(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)),
recordFilterBox, SIGNAL(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)));
connect(this, SIGNAL(useFilterRequest(const std::string&)), recordFilterBox, SIGNAL(useFilterRequest(const std::string&)));
setAcceptDrops(true); setAcceptDrops(true);
} }
void CSVFilter::FilterBox::setRecordFilter (const std::string& filter)
{
mRecordFilterBox->setFilter (filter);
}
void CSVFilter::FilterBox::dropEvent (QDropEvent* event) void CSVFilter::FilterBox::dropEvent (QDropEvent* event)
{ {
std::vector<CSMWorld::UniversalId> data = dynamic_cast<const CSMWorld::TableMimeData*> (event->mimeData())->getData(); std::vector<CSMWorld::UniversalId> data = dynamic_cast<const CSMWorld::TableMimeData*> (event->mimeData())->getData();
@ -48,3 +49,9 @@ void CSVFilter::FilterBox::dragMoveEvent (QDragMoveEvent* event)
{ {
event->accept(); event->accept();
} }
void CSVFilter::FilterBox::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource,
Qt::DropAction action)
{
mRecordFilterBox->createFilterRequest(filterSource, action);
}

@ -16,27 +16,33 @@ namespace CSMWorld
namespace CSVFilter namespace CSVFilter
{ {
class RecordFilterBox;
class FilterBox : public QWidget class FilterBox : public QWidget
{ {
Q_OBJECT Q_OBJECT
RecordFilterBox *mRecordFilterBox;
public:
FilterBox (CSMWorld::Data& data, QWidget *parent = 0);
void setRecordFilter (const std::string& filter);
void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
Qt::DropAction action);
private:
void dragEnterEvent (QDragEnterEvent* event); void dragEnterEvent (QDragEnterEvent* event);
void dropEvent (QDropEvent* event); void dropEvent (QDropEvent* event);
void dragMoveEvent(QDragMoveEvent *event); void dragMoveEvent(QDragMoveEvent *event);
public:
FilterBox (CSMWorld::Data& data, QWidget *parent = 0);
signals: signals:
void recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter); void recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter);
void recordDropped (std::vector<CSMWorld::UniversalId>& types, Qt::DropAction action); void recordDropped (std::vector<CSMWorld::UniversalId>& types, Qt::DropAction action);
void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
Qt::DropAction action);
void useFilterRequest(const std::string& idOfFilter);
}; };
} }

@ -15,18 +15,25 @@ CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *pare
layout->addWidget (new QLabel ("Record Filter", this)); layout->addWidget (new QLabel ("Record Filter", this));
EditWidget *editWidget = new EditWidget (data, this); mEdit = new EditWidget (data, this);
layout->addWidget (editWidget); layout->addWidget (mEdit);
setLayout (layout); setLayout (layout);
connect ( connect (
editWidget, SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)), mEdit, SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)),
this, SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>))); this, SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)));
}
connect(this, SIGNAL(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)), void CSVFilter::RecordFilterBox::setFilter (const std::string& filter)
editWidget, SLOT(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction))); {
mEdit->clear();
mEdit->setText (QString::fromUtf8 (filter.c_str()));
}
connect(this, SIGNAL(useFilterRequest(const std::string&)), editWidget, SLOT(useFilterRequest(const std::string&))); void CSVFilter::RecordFilterBox::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource,
Qt::DropAction action)
{
mEdit->createFilterRequest(filterSource, action);
} }

@ -17,20 +17,28 @@ namespace CSMWorld
namespace CSVFilter namespace CSVFilter
{ {
class EditWidget;
class RecordFilterBox : public QWidget class RecordFilterBox : public QWidget
{ {
Q_OBJECT Q_OBJECT
EditWidget *mEdit;
public: public:
RecordFilterBox (CSMWorld::Data& data, QWidget *parent = 0); RecordFilterBox (CSMWorld::Data& data, QWidget *parent = 0);
signals: void setFilter (const std::string& filter);
void useFilterRequest(const std::string& idOfFilter);
void filterChanged (boost::shared_ptr<CSMFilter::Node> filter);
void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource, void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
Qt::DropAction action); Qt::DropAction action);
void useFilterRequest(const std::string& idOfFilter);
signals:
void filterChanged (boost::shared_ptr<CSMFilter::Node> filter);
}; };
} }

@ -1,6 +1,47 @@
#include "pagedworldspacewidget.hpp" #include "pagedworldspacewidget.hpp"
#include <sstream>
CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget *parent) CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget *parent)
: WorldspaceWidget (parent) : WorldspaceWidget (parent)
{} {}
void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint)
{
if (!hint.empty())
{
CSMWorld::CellSelection selection;
if (hint[0]=='c')
{
// syntax: c:#x1 y1; #x2 y2 (number of coordinate pairs can be 0 or larger)
char ignore;
std::istringstream stream (hint.c_str());
if (stream >> ignore)
{
char ignore1; // : or ;
char ignore2; // #
int x, y;
while (stream >> ignore1 >> ignore2 >> x >> y)
selection.add (CSMWorld::CellCoordinates (x, y));
/// \todo adjust camera position
}
}
else if (hint[0]=='r')
{
/// \todo implement 'r' type hints
}
setCellSelection (selection);
}
}
void CSVRender::PagedWorldspaceWidget::setCellSelection (const CSMWorld::CellSelection& selection)
{
mSelection = selection;
emit cellSelectionChanged (mSelection);
}

@ -1,6 +1,8 @@
#ifndef OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H #ifndef OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H
#define OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H #define OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H
#include "../../model/world/cellselection.hpp"
#include "worldspacewidget.hpp" #include "worldspacewidget.hpp"
namespace CSVRender namespace CSVRender
@ -9,9 +11,22 @@ namespace CSVRender
{ {
Q_OBJECT Q_OBJECT
CSMWorld::CellSelection mSelection;
public: public:
PagedWorldspaceWidget (QWidget *parent); PagedWorldspaceWidget (QWidget *parent);
///< \note Sets the cell area selection to an invalid value to indicate that currently
/// no cells are displayed. The cells to be displayed will be specified later through
/// hint system.
virtual void useViewHint (const std::string& hint);
void setCellSelection (const CSMWorld::CellSelection& selection);
signals:
void cellSelectionChanged (const CSMWorld::CellSelection& selection);
}; };
} }

@ -26,6 +26,8 @@ void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode)
setNavigation (&mOrbit); setNavigation (&mOrbit);
} }
void CSVRender::WorldspaceWidget::useViewHint (const std::string& hint) {}
void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() void CSVRender::WorldspaceWidget::selectDefaultNavigationMode()
{ {
setNavigation (&m1st); setNavigation (&m1st);

@ -33,6 +33,9 @@ namespace CSVRender
void selectDefaultNavigationMode(); void selectDefaultNavigationMode();
virtual void useViewHint (const std::string& hint);
///< Default-implementation: ignored.
private slots: private slots:
void selectNavigationMode (const std::string& mode); void selectNavigationMode (const std::string& mode);

@ -28,7 +28,7 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo
else else
mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), this); mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), this);
SceneToolbar *toolbar = new SceneToolbar (48, this); SceneToolbar *toolbar = new SceneToolbar (48+6, this);
SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar);
toolbar->addTool (lightingTool); toolbar->addTool (lightingTool);

@ -0,0 +1,346 @@
#include "regionmap.hpp"
#include <algorithm>
#include <set>
#include <sstream>
#include <QHeaderView>
#include <QContextMenuEvent>
#include <QMenu>
#include "../../model/doc/document.hpp"
#include "../../model/world/regionmap.hpp"
#include "../../model/world/universalid.hpp"
#include "../../model/world/data.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/columns.hpp"
void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event)
{
QMenu menu (this);
if (getUnselectedCells().size()>0)
menu.addAction (mSelectAllAction);
if (selectionModel()->selectedIndexes().size()>0)
menu.addAction (mClearSelectionAction);
if (getMissingRegionCells().size()>0)
menu.addAction (mSelectRegionsAction);
int selectedNonExistentCells = getSelectedCells (false, true).size();
if (selectedNonExistentCells>0)
{
if (selectedNonExistentCells==1)
mCreateCellsAction->setText ("Create one Cell");
else
{
std::ostringstream stream;
stream << "Create " << selectedNonExistentCells << " cells";
mCreateCellsAction->setText (QString::fromUtf8 (stream.str().c_str()));
}
menu.addAction (mCreateCellsAction);
}
if (getSelectedCells().size()>0)
{
if (!mRegionId.empty())
{
mSetRegionAction->setText (QString::fromUtf8 (("Set Region to " + mRegionId).c_str()));
menu.addAction (mSetRegionAction);
}
menu.addAction (mUnsetRegionAction);
menu.addAction (mViewInTableAction);
}
if (selectionModel()->selectedIndexes().size()>0)
menu.addAction (mViewAction);
menu.exec (event->globalPos());
}
QModelIndexList CSVWorld::RegionMap::getUnselectedCells() const
{
const QAbstractItemModel *model = QTableView::model();
int rows = model->rowCount();
int columns = model->columnCount();
QModelIndexList selected = selectionModel()->selectedIndexes();
std::sort (selected.begin(), selected.end());
QModelIndexList all;
for (int y=0; y<rows; ++y)
for (int x=0; x<columns; ++x)
{
QModelIndex index = model->index (y, x);
if (model->data (index, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern))
all.push_back (index);
}
std::sort (all.begin(), all.end());
QModelIndexList list;
std::set_difference (all.begin(), all.end(), selected.begin(), selected.end(),
std::back_inserter (list));
return list;
}
QModelIndexList CSVWorld::RegionMap::getSelectedCells (bool existent, bool nonExistent) const
{
const QAbstractItemModel *model = QTableView::model();
QModelIndexList selected = selectionModel()->selectedIndexes();
QModelIndexList list;
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
{
bool exists = model->data (*iter, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern);
if ((exists && existent) || (!exists && nonExistent))
list.push_back (*iter);
}
return list;
}
QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const
{
const QAbstractItemModel *model = QTableView::model();
QModelIndexList selected = selectionModel()->selectedIndexes();
std::set<std::string> regions;
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
{
std::string region =
model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData();
if (!region.empty())
regions.insert (region);
}
QModelIndexList list;
QModelIndexList unselected = getUnselectedCells();
for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter)
{
std::string region =
model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData();
if (!region.empty() && regions.find (region)!=regions.end())
list.push_back (*iter);
}
return list;
}
void CSVWorld::RegionMap::setRegion (const std::string& regionId)
{
QModelIndexList selected = getSelectedCells();
QAbstractItemModel *regionModel = model();
CSMWorld::IdTable *cellsModel = &dynamic_cast<CSMWorld::IdTable&> (*
mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells));
QString regionId2 = QString::fromUtf8 (regionId.c_str());
if (selected.size()>1)
mDocument.getUndoStack().beginMacro (tr ("Set Region"));
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
{
std::string cellId = regionModel->data (*iter, CSMWorld::RegionMap::Role_CellId).
toString().toUtf8().constData();
QModelIndex index = cellsModel->getModelIndex (cellId,
cellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Region));
mDocument.getUndoStack().push (
new CSMWorld::ModifyCommand (*cellsModel, index, regionId2));
}
if (selected.size()>1)
mDocument.getUndoStack().endMacro();
}
CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId,
CSMDoc::Document& document, QWidget *parent)
: QTableView (parent), mEditLock (false), mDocument (document)
{
verticalHeader()->hide();
horizontalHeader()->hide();
setSelectionMode (QAbstractItemView::ExtendedSelection);
setModel (document.getData().getTableModel (universalId));
resizeColumnsToContents();
resizeRowsToContents();
mSelectAllAction = new QAction (tr ("Select All"), this);
connect (mSelectAllAction, SIGNAL (triggered()), this, SLOT (selectAll()));
addAction (mSelectAllAction);
mClearSelectionAction = new QAction (tr ("Clear Selection"), this);
connect (mClearSelectionAction, SIGNAL (triggered()), this, SLOT (clearSelection()));
addAction (mClearSelectionAction);
mSelectRegionsAction = new QAction (tr ("Select Regions"), this);
connect (mSelectRegionsAction, SIGNAL (triggered()), this, SLOT (selectRegions()));
addAction (mSelectRegionsAction);
mCreateCellsAction = new QAction (tr ("Create Cells Action"), this);
connect (mCreateCellsAction, SIGNAL (triggered()), this, SLOT (createCells()));
addAction (mCreateCellsAction);
mSetRegionAction = new QAction (tr ("Set Region"), this);
connect (mSetRegionAction, SIGNAL (triggered()), this, SLOT (setRegion()));
addAction (mSetRegionAction);
mUnsetRegionAction = new QAction (tr ("Unset Region"), this);
connect (mUnsetRegionAction, SIGNAL (triggered()), this, SLOT (unsetRegion()));
addAction (mUnsetRegionAction);
mViewAction = new QAction (tr ("View Cells"), this);
connect (mViewAction, SIGNAL (triggered()), this, SLOT (view()));
addAction (mViewAction);
mViewInTableAction = new QAction (tr ("View Cells in Table"), this);
connect (mViewInTableAction, SIGNAL (triggered()), this, SLOT (viewInTable()));
addAction (mViewInTableAction);
}
void CSVWorld::RegionMap::setEditLock (bool locked)
{
mEditLock = locked;
}
void CSVWorld::RegionMap::selectAll()
{
QModelIndexList unselected = getUnselectedCells();
for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter)
selectionModel()->select (*iter, QItemSelectionModel::Select);
}
void CSVWorld::RegionMap::clearSelection()
{
selectionModel()->clearSelection();
}
void CSVWorld::RegionMap::selectRegions()
{
QModelIndexList unselected = getMissingRegionCells();
for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter)
selectionModel()->select (*iter, QItemSelectionModel::Select);
}
void CSVWorld::RegionMap::createCells()
{
if (mEditLock)
return;
QModelIndexList selected = getSelectedCells (false, true);
CSMWorld::IdTable *cellsModel = &dynamic_cast<CSMWorld::IdTable&> (*
mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells));
if (selected.size()>1)
mDocument.getUndoStack().beginMacro (tr ("Create cells"));
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
{
std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId).
toString().toUtf8().constData();
mDocument.getUndoStack().push (new CSMWorld::CreateCommand (*cellsModel, cellId));
}
if (selected.size()>1)
mDocument.getUndoStack().endMacro();
}
void CSVWorld::RegionMap::setRegion()
{
if (mEditLock)
return;
setRegion (mRegionId);
}
void CSVWorld::RegionMap::unsetRegion()
{
if (mEditLock)
return;
setRegion ("");
}
void CSVWorld::RegionMap::view()
{
std::ostringstream hint;
hint << "c:";
QModelIndexList selected = selectionModel()->selectedIndexes();
bool first = true;
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
{
std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId).
toString().toUtf8().constData();
if (first)
first = false;
else
hint << "; ";
hint << cellId;
}
emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, "sys::default"),
hint.str());
}
void CSVWorld::RegionMap::viewInTable()
{
std::ostringstream hint;
hint << "f:!or(";
QModelIndexList selected = getSelectedCells();
bool first = true;
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
{
std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId).
toString().toUtf8().constData();
if (first)
first = false;
else
hint << ",";
hint << "string(ID,\"" << cellId << "\")";
}
hint << ")";
emit editRequest (CSMWorld::UniversalId::Type_Cells, hint.str());
}

@ -0,0 +1,84 @@
#ifndef CSV_WORLD_REGIONMAP_H
#define CSV_WORLD_REGIONMAP_H
#include <QTableView>
class QAction;
namespace CSMDoc
{
class Document;
}
namespace CSMWorld
{
class UniversalId;
}
namespace CSVWorld
{
class RegionMap : public QTableView
{
Q_OBJECT
QAction *mSelectAllAction;
QAction *mClearSelectionAction;
QAction *mSelectRegionsAction;
QAction *mCreateCellsAction;
QAction *mSetRegionAction;
QAction *mUnsetRegionAction;
QAction *mViewAction;
QAction *mViewInTableAction;
bool mEditLock;
CSMDoc::Document& mDocument;
std::string mRegionId;
private:
void contextMenuEvent (QContextMenuEvent *event);
QModelIndexList getUnselectedCells() const;
///< \note Non-existent cells are not listed.
QModelIndexList getSelectedCells (bool existent = true, bool nonExistent = false) const;
///< \param existant Include existant cells.
/// \param nonExistant Include non-existant cells.
QModelIndexList getMissingRegionCells() const;
///< Unselected cells within all regions that have at least one selected cell.
void setRegion (const std::string& regionId);
///< Set region Id of selected cells.
public:
RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document,
QWidget *parent = 0);
void setEditLock (bool locked);
signals:
void editRequest (const CSMWorld::UniversalId& id, const std::string& hint);
private slots:
void selectAll();
void clearSelection();
void selectRegions();
void createCells();
void setRegion();
void unsetRegion();
void view();
void viewInTable();
};
}
#endif

@ -1,29 +1,27 @@
#include "regionmapsubview.hpp" #include "regionmapsubview.hpp"
#include <QTableView> #include "regionmap.hpp"
#include <QHeaderView>
CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId, CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId,
CSMDoc::Document& document) CSMDoc::Document& document)
: CSVDoc::SubView (universalId) : CSVDoc::SubView (universalId)
{ {
mTable = new QTableView (this); mRegionMap = new RegionMap (universalId, document, this);
mTable->verticalHeader()->hide(); setWidget (mRegionMap);
mTable->horizontalHeader()->hide();
mTable->setSelectionMode (QAbstractItemView::ExtendedSelection); connect (mRegionMap, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)),
this, SLOT (editRequest (const CSMWorld::UniversalId&, const std::string&)));
mTable->setModel (document.getData().getTableModel (universalId));
mTable->resizeColumnsToContents();
mTable->resizeRowsToContents();
setWidget (mTable);
} }
void CSVWorld::RegionMapSubView::setEditLock (bool locked) void CSVWorld::RegionMapSubView::setEditLock (bool locked)
{ {
mRegionMap->setEditLock (locked);
}
void CSVWorld::RegionMapSubView::editRequest (const CSMWorld::UniversalId& id,
const std::string& hint)
{
focusId (id, hint);
} }

@ -3,7 +3,7 @@
#include "../doc/subview.hpp" #include "../doc/subview.hpp"
class QTableView; class QAction;
namespace CSMDoc namespace CSMDoc
{ {
@ -12,15 +12,23 @@ namespace CSMDoc
namespace CSVWorld namespace CSVWorld
{ {
class RegionMap;
class RegionMapSubView : public CSVDoc::SubView class RegionMapSubView : public CSVDoc::SubView
{ {
QTableView *mTable; Q_OBJECT
RegionMap *mRegionMap;
public: public:
RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document); RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document);
virtual void setEditLock (bool locked); virtual void setEditLock (bool locked);
private slots:
void editRequest (const CSMWorld::UniversalId& id, const std::string& hint);
}; };
} }

@ -1,12 +1,16 @@
#include "scenesubview.hpp" #include "scenesubview.hpp"
#include <sstream>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include "../../model/doc/document.hpp" #include "../../model/doc/document.hpp"
#include "../../model/world/cellselection.hpp"
#include "../filter/filterbox.hpp" #include "../filter/filterbox.hpp"
#include "../render/pagedworldspacewidget.hpp" #include "../render/pagedworldspacewidget.hpp"
@ -32,10 +36,15 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D
layout2->setContentsMargins (QMargins (0, 0, 0, 0)); layout2->setContentsMargins (QMargins (0, 0, 0, 0));
SceneToolbar *toolbar = new SceneToolbar (48, this); SceneToolbar *toolbar = new SceneToolbar (48+6, this);
if (id.getId()=="sys::default") if (id.getId()=="sys::default")
mScene = new CSVRender::PagedWorldspaceWidget (this); {
CSVRender::PagedWorldspaceWidget *widget = new CSVRender::PagedWorldspaceWidget (this);
mScene = widget;
connect (widget, SIGNAL (cellSelectionChanged (const CSMWorld::CellSelection&)),
this, SLOT (cellSelectionChanged (const CSMWorld::CellSelection&)));
}
else else
mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this);
@ -83,7 +92,38 @@ void CSVWorld::SceneSubView::setStatusBar (bool show)
mBottom->setStatusBar (show); mBottom->setStatusBar (show);
} }
void CSVWorld::SceneSubView::useHint (const std::string& hint)
{
mScene->useViewHint (hint);
}
void CSVWorld::SceneSubView::closeRequest() void CSVWorld::SceneSubView::closeRequest()
{ {
deleteLater(); deleteLater();
}
void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection& selection)
{
int size = selection.getSize();
std::ostringstream stream;
stream << "Scene: " << getUniversalId().getId();
if (size==0)
stream << " (empty)";
else if (size==1)
{
stream << " (" << *selection.begin() << ")";
}
else
{
stream << " (" << selection.getCentre() << " and " << size-1 << " more ";
if (size>1)
stream << "cells around it)";
else
stream << "cell around it)";
}
setWindowTitle (QString::fromUtf8 (stream.str().c_str()));
} }

@ -5,6 +5,11 @@
class QModelIndex; class QModelIndex;
namespace CSMWorld
{
class CellSelection;
}
namespace CSMDoc namespace CSMDoc
{ {
class Document; class Document;
@ -38,9 +43,13 @@ namespace CSVWorld
virtual void setStatusBar (bool show); virtual void setStatusBar (bool show);
virtual void useHint (const std::string& hint);
private slots: private slots:
void closeRequest(); void closeRequest();
void cellSelectionChanged (const CSMWorld::CellSelection& selection);
}; };
} }

@ -6,6 +6,7 @@
CSVWorld::SceneTool::SceneTool (SceneToolbar *parent) : QPushButton (parent) CSVWorld::SceneTool::SceneTool (SceneToolbar *parent) : QPushButton (parent)
{ {
setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed));
setIconSize (QSize (parent->getIconSize(), parent->getIconSize()));
setFixedSize (parent->getButtonSize(), parent->getButtonSize()); setFixedSize (parent->getButtonSize(), parent->getButtonSize());
connect (this, SIGNAL (clicked()), this, SLOT (openRequest())); connect (this, SIGNAL (clicked()), this, SLOT (openRequest()));

@ -6,7 +6,7 @@
#include "scenetool.hpp" #include "scenetool.hpp"
CSVWorld::SceneToolbar::SceneToolbar (int buttonSize, QWidget *parent) CSVWorld::SceneToolbar::SceneToolbar (int buttonSize, QWidget *parent)
: QWidget (parent), mButtonSize (buttonSize) : QWidget (parent), mButtonSize (buttonSize), mIconSize (buttonSize-6)
{ {
setFixedWidth (mButtonSize); setFixedWidth (mButtonSize);
@ -26,4 +26,9 @@ void CSVWorld::SceneToolbar::addTool (SceneTool *tool)
int CSVWorld::SceneToolbar::getButtonSize() const int CSVWorld::SceneToolbar::getButtonSize() const
{ {
return mButtonSize; return mButtonSize;
}
int CSVWorld::SceneToolbar::getIconSize() const
{
return mIconSize;
} }

@ -15,6 +15,7 @@ namespace CSVWorld
QVBoxLayout *mLayout; QVBoxLayout *mLayout;
int mButtonSize; int mButtonSize;
int mIconSize;
public: public:
@ -23,6 +24,8 @@ namespace CSVWorld
void addTool (SceneTool *tool); void addTool (SceneTool *tool);
int getButtonSize() const; int getButtonSize() const;
int getIconSize() const;
}; };
} }

@ -8,7 +8,7 @@
#include "scenetoolbar.hpp" #include "scenetoolbar.hpp"
CSVWorld::SceneToolMode::SceneToolMode (SceneToolbar *parent) CSVWorld::SceneToolMode::SceneToolMode (SceneToolbar *parent)
: SceneTool (parent), mButtonSize (parent->getButtonSize()) : SceneTool (parent), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize())
{ {
mPanel = new QFrame (this, Qt::Popup); mPanel = new QFrame (this, Qt::Popup);
@ -29,6 +29,7 @@ void CSVWorld::SceneToolMode::addButton (const std::string& icon, const std::str
{ {
QPushButton *button = new QPushButton (QIcon (QPixmap (icon.c_str())), "", mPanel); QPushButton *button = new QPushButton (QIcon (QPixmap (icon.c_str())), "", mPanel);
button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed));
button->setIconSize (QSize (mIconSize, mIconSize));
button->setFixedSize (mButtonSize, mButtonSize); button->setFixedSize (mButtonSize, mButtonSize);
mLayout->addWidget (button); mLayout->addWidget (button);

@ -20,6 +20,7 @@ namespace CSVWorld
QHBoxLayout *mLayout; QHBoxLayout *mLayout;
std::map<QPushButton *, std::string> mButtons; // widget, id std::map<QPushButton *, std::string> mButtons; // widget, id
int mButtonSize; int mButtonSize;
int mIconSize;
public: public:

@ -358,6 +358,9 @@ void CSVWorld::Table::cloneRecord()
void CSVWorld::Table::moveUpRecord() void CSVWorld::Table::moveUpRecord()
{ {
if (mEditLock)
return;
QModelIndexList selectedRows = selectionModel()->selectedRows(); QModelIndexList selectedRows = selectionModel()->selectedRows();
if (selectedRows.size()==1) if (selectedRows.size()==1)
@ -387,6 +390,9 @@ void CSVWorld::Table::moveUpRecord()
void CSVWorld::Table::moveDownRecord() void CSVWorld::Table::moveDownRecord()
{ {
if (mEditLock)
return;
QModelIndexList selectedRows = selectionModel()->selectedRows(); QModelIndexList selectedRows = selectionModel()->selectedRows();
if (selectedRows.size()==1) if (selectedRows.size()==1)

@ -26,9 +26,9 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
layout->insertWidget (0, mTable = layout->insertWidget (0, mTable =
new Table (id, mBottom->canCreateAndDelete(), sorting, document), 2); new Table (id, mBottom->canCreateAndDelete(), sorting, document), 2);
CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); mFilterBox = new CSVFilter::FilterBox (document.getData(), this);
layout->insertWidget (0, filterBox); layout->insertWidget (0, mFilterBox);
QWidget *widget = new QWidget; QWidget *widget = new QWidget;
@ -48,7 +48,7 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
mTable->selectionSizeUpdate(); mTable->selectionSizeUpdate();
mTable->viewport()->installEventFilter(this); mTable->viewport()->installEventFilter(this);
mBottom->installEventFilter(this); mBottom->installEventFilter(this);
filterBox->installEventFilter(this); mFilterBox->installEventFilter(this);
if (mBottom->canCreateAndDelete()) if (mBottom->canCreateAndDelete())
{ {
@ -63,17 +63,12 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
connect (mBottom, SIGNAL (requestFocus (const std::string&)), connect (mBottom, SIGNAL (requestFocus (const std::string&)),
mTable, SLOT (requestFocus (const std::string&))); mTable, SLOT (requestFocus (const std::string&)));
connect (filterBox, connect (mFilterBox,
SIGNAL (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>)), SIGNAL (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>)),
mTable, SLOT (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>))); mTable, SLOT (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>)));
connect(filterBox, SIGNAL(recordDropped(std::vector<CSMWorld::UniversalId>&, Qt::DropAction)), connect(mFilterBox, SIGNAL(recordDropped(std::vector<CSMWorld::UniversalId>&, Qt::DropAction)),
this, SLOT(createFilterRequest(std::vector<CSMWorld::UniversalId>&, Qt::DropAction))); this, SLOT(createFilterRequest(std::vector<CSMWorld::UniversalId>&, Qt::DropAction)));
connect(this, SIGNAL(useFilterRequest(const std::string&)), filterBox, SIGNAL(useFilterRequest(const std::string&)));
connect(this, SIGNAL(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)),
filterBox, SIGNAL(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)));
} }
void CSVWorld::TableSubView::setEditLock (bool locked) void CSVWorld::TableSubView::setEditLock (bool locked)
@ -97,6 +92,15 @@ void CSVWorld::TableSubView::setStatusBar (bool show)
mBottom->setStatusBar (show); mBottom->setStatusBar (show);
} }
void CSVWorld::TableSubView::useHint (const std::string& hint)
{
if (hint.empty())
return;
if (hint[0]=='f' && hint.size()>=2)
mFilterBox->setRecordFilter (hint.substr (2));
}
void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone)
{ {
emit cloneRequest(toClone.getId(), toClone.getType()); emit cloneRequest(toClone.getId(), toClone.getType());
@ -113,7 +117,7 @@ void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::Univers
filterSource.push_back(pair); filterSource.push_back(pair);
} }
emit createFilterRequest(filterSource, action); mFilterBox->createFilterRequest(filterSource, action);
} }
bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event)
@ -125,7 +129,7 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event)
bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter);
if (handled) if (handled)
{ {
emit useFilterRequest(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); mFilterBox->setRecordFilter(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId());
} }
return handled; return handled;
} }

@ -17,6 +17,11 @@ namespace CSMDoc
class Document; class Document;
} }
namespace CSVFilter
{
class FilterBox;
}
namespace CSVWorld namespace CSVWorld
{ {
class Table; class Table;
@ -29,6 +34,7 @@ namespace CSVWorld
Table *mTable; Table *mTable;
TableBottomBox *mBottom; TableBottomBox *mBottom;
CSVFilter::FilterBox *mFilterBox;
public: public:
@ -41,15 +47,14 @@ namespace CSVWorld
virtual void setStatusBar (bool show); virtual void setStatusBar (bool show);
virtual void useHint (const std::string& hint);
protected: protected:
bool eventFilter(QObject* object, QEvent *event); bool eventFilter(QObject* object, QEvent *event);
signals: signals:
void cloneRequest(const std::string&, void cloneRequest(const std::string&,
const CSMWorld::UniversalId::Type); const CSMWorld::UniversalId::Type);
void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
Qt::DropAction action);
void useFilterRequest(const std::string& idOfFilter);
private slots: private slots:

@ -108,7 +108,11 @@ void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemM
{ {
NastyTableModelHack hack (*model); NastyTableModelHack hack (*model);
QStyledItemDelegate::setModelData (editor, &hack, index); QStyledItemDelegate::setModelData (editor, &hack, index);
mUndoStack.push (new CSMWorld::ModifyCommand (*model, index, hack.getData()));
QVariant new_ = hack.getData();
if (model->data (index)!=new_)
mUndoStack.push (new CSMWorld::ModifyCommand (*model, index, new_));
} }
CSVWorld::CommandDelegate::CommandDelegate (QUndoStack& undoStack, QObject *parent) CSVWorld::CommandDelegate::CommandDelegate (QUndoStack& undoStack, QObject *parent)

@ -33,7 +33,7 @@ add_openmw_dir (mwgui
merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks
keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview
tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog
recharge mode videowidget recharge mode videowidget backgroundimage
) )
add_openmw_dir (mwdialogue add_openmw_dir (mwdialogue
@ -67,8 +67,8 @@ add_openmw_dir (mwclass
add_openmw_dir (mwmechanics add_openmw_dir (mwmechanics
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow drawstate spells activespells npcstats aipackage aisequence aipersue alchemy aiwander aitravel aifollow
aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
disease pickpocket levelledlist combat steering disease pickpocket levelledlist combat steering
) )

@ -134,10 +134,6 @@ namespace MWClass
getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr), "", getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr), "",
MWBase::Environment::get().getWorld()->getStore()); MWBase::Environment::get().getWorld()->getStore());
// TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory.
// (except for gold you gave him)
getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, ref->mBase->mData.mGold, ptr);
if (ref->mBase->mFlags & ESM::Creature::Weapon) if (ref->mBase->mFlags & ESM::Creature::Weapon)
getInventoryStore(ptr).autoEquip(ptr); getInventoryStore(ptr).autoEquip(ptr);
} }
@ -811,6 +807,11 @@ namespace MWClass
customData.mCreatureStats.writeState (state2.mCreatureStats); customData.mCreatureStats.writeState (state2.mCreatureStats);
} }
int Creature::getBaseGold(const MWWorld::Ptr& ptr) const
{
return ptr.get<ESM::Creature>()->mBase->mData.mGold;
}
const ESM::GameSetting* Creature::fMinWalkSpeedCreature; const ESM::GameSetting* Creature::fMinWalkSpeedCreature;
const ESM::GameSetting* Creature::fMaxWalkSpeedCreature; const ESM::GameSetting* Creature::fMaxWalkSpeedCreature;
const ESM::GameSetting *Creature::fEncumberedMoveEffect; const ESM::GameSetting *Creature::fEncumberedMoveEffect;

@ -141,6 +141,8 @@ namespace MWClass
virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
const; const;
///< Write additional state from \a ptr into \a state. ///< Write additional state from \a ptr into \a state.
virtual int getBaseGold(const MWWorld::Ptr& ptr) const;
}; };
} }

@ -365,13 +365,7 @@ namespace MWClass
// store // store
ptr.getRefData().setCustomData (data.release()); ptr.getRefData().setCustomData (data.release());
// TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory. getInventoryStore(ptr).autoEquip(ptr);
// (except for gold you gave him)
getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, gold, ptr);
getInventoryStore(ptr).autoEquip(ptr);
} }
} }
@ -818,7 +812,13 @@ namespace MWClass
return boost::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction("#{sActorInCombat}")); return boost::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction("#{sActorInCombat}"));
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)) if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak))
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
// player got activated by another NPC
if(ptr.getRefData().getHandle() == "player")
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(actor));
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr)); return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
} }
MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr) MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)
@ -1294,6 +1294,20 @@ namespace MWClass
static_cast<const MWMechanics::CreatureStats&> (customData.mNpcStats).writeState (state2.mCreatureStats); static_cast<const MWMechanics::CreatureStats&> (customData.mNpcStats).writeState (state2.mCreatureStats);
} }
int Npc::getBaseGold(const MWWorld::Ptr& ptr) const
{
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
return ref->mBase->mNpdt52.mGold;
else
return ref->mBase->mNpdt12.mGold;
}
bool Npc::isClass(const MWWorld::Ptr& ptr, const std::string &className) const
{
return ptr.get<ESM::NPC>()->mBase->mClass == className;
}
const ESM::GameSetting *Npc::fMinWalkSpeed; const ESM::GameSetting *Npc::fMinWalkSpeed;
const ESM::GameSetting *Npc::fMaxWalkSpeed; const ESM::GameSetting *Npc::fMaxWalkSpeed;
const ESM::GameSetting *Npc::fEncumberedMoveEffect; const ESM::GameSetting *Npc::fEncumberedMoveEffect;

@ -166,6 +166,10 @@ namespace MWClass
virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
const; const;
///< Write additional state from \a ptr into \a state. ///< Write additional state from \a ptr into \a state.
virtual int getBaseGold(const MWWorld::Ptr& ptr) const;
virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const;
}; };
} }

@ -500,7 +500,24 @@ namespace MWDialogue
mTemporaryDispositionChange = 100 - curDisp; mTemporaryDispositionChange = 100 - curDisp;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::Class::get(player).skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1);
if (success)
{
int gold=0;
if (type == MWBase::MechanicsManager::PT_Bribe10)
gold = 10;
else if (type == MWBase::MechanicsManager::PT_Bribe100)
gold = 100;
else if (type == MWBase::MechanicsManager::PT_Bribe1000)
gold = 1000;
if (gold)
{
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, gold, player);
mActor.getClass().getContainerStore(mActor).add(MWWorld::ContainerStore::sGoldId, gold, mActor);
}
}
std::string text; std::string text;

@ -0,0 +1,63 @@
#include "backgroundimage.hpp"
#include <MyGUI_Gui.h>
namespace MWGui
{
void BackgroundImage::setBackgroundImage (const std::string& image, bool fixedRatio, bool correct)
{
if (mChild)
{
MyGUI::Gui::getInstance().destroyWidget(mChild);
mChild = NULL;
}
if (correct)
{
setImageTexture("black.png");
if (fixedRatio)
mAspect = 4.0/3.0;
else
mAspect = 0; // TODO
mChild = createWidgetReal<MyGUI::ImageBox>("ImageBox",
MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default);
mChild->setImageTexture(image);
adjustSize();
}
else
{
mAspect = 0;
setImageTexture(image);
}
}
void BackgroundImage::adjustSize()
{
if (mAspect == 0)
return;
MyGUI::IntSize screenSize = getSize();
int leftPadding = std::max(0.0, (screenSize.width - screenSize.height * mAspect) / 2);
int topPadding = std::max(0.0, (screenSize.height - screenSize.width / mAspect) / 2);
mChild->setCoord(leftPadding, topPadding, screenSize.width - leftPadding*2, screenSize.height - topPadding*2);
}
void BackgroundImage::setSize (const MyGUI::IntSize& _value)
{
MyGUI::Widget::setSize (_value);
adjustSize();
}
void BackgroundImage::setCoord (const MyGUI::IntCoord& _value)
{
MyGUI::Widget::setCoord (_value);
adjustSize();
}
}

@ -0,0 +1,37 @@
#ifndef OPENMW_MWGUI_BACKGROUNDIMAGE_H
#define OPENMW_MWGUI_BACKGROUNDIMAGE_H
#include <MyGUI_ImageBox.h>
namespace MWGui
{
/**
* @brief A variant of MyGUI::ImageBox with aspect ratio correction using black bars
*/
class BackgroundImage : public MyGUI::ImageBox
{
MYGUI_RTTI_DERIVED(BackgroundImage)
public:
BackgroundImage() : mChild(NULL), mAspect(0) {}
/**
* @param fixedRatio Use a fixed ratio of 4:3, regardless of the image dimensions
* @param correct Add black bars?
*/
void setBackgroundImage (const std::string& image, bool fixedRatio=true, bool correct=true);
virtual void setSize (const MyGUI::IntSize &_value);
virtual void setCoord (const MyGUI::IntCoord &_value);
private:
MyGUI::ImageBox* mChild;
double mAspect;
void adjustSize();
};
}
#endif

@ -56,26 +56,16 @@ namespace MWGui
void PersuasionDialog::onPersuade(MyGUI::Widget *sender) void PersuasionDialog::onPersuade(MyGUI::Widget *sender)
{ {
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWBase::MechanicsManager::PersuasionType type; MWBase::MechanicsManager::PersuasionType type;
if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire; if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire;
else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate; else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate;
else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt; else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt;
else if (sender == mBribe10Button) else if (sender == mBribe10Button)
{
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 10, player);
type = MWBase::MechanicsManager::PT_Bribe10; type = MWBase::MechanicsManager::PT_Bribe10;
}
else if (sender == mBribe100Button) else if (sender == mBribe100Button)
{
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 100, player);
type = MWBase::MechanicsManager::PT_Bribe100; type = MWBase::MechanicsManager::PT_Bribe100;
}
else /*if (sender == mBribe1000Button)*/ else /*if (sender == mBribe1000Button)*/
{
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 1000, player);
type = MWBase::MechanicsManager::PT_Bribe1000; type = MWBase::MechanicsManager::PT_Bribe1000;
}
MWBase::Environment::get().getDialogueManager()->persuade(type); MWBase::Environment::get().getDialogueManager()->persuade(type);

@ -131,7 +131,22 @@ namespace MWGui
const ESM::Class *cls = const ESM::Class *cls =
world->getStore().get<ESM::Class>().find(playerData->mClass); world->getStore().get<ESM::Class>().find(playerData->mClass);
mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); // 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))
{
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");
}
else
mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds");
int level = creatureStats.getLevel ()+1; int level = creatureStats.getLevel ()+1;
mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast<std::string>(level)); mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast<std::string>(level));

@ -9,14 +9,14 @@
#include <OgreViewport.h> #include <OgreViewport.h>
#include <OgreHardwarePixelBuffer.h> #include <OgreHardwarePixelBuffer.h>
#include <openengine/ogre/fader.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
#include "backgroundimage.hpp"
namespace MWGui namespace MWGui
{ {
@ -32,28 +32,13 @@ namespace MWGui
{ {
getWidget(mLoadingText, "LoadingText"); getWidget(mLoadingText, "LoadingText");
getWidget(mProgressBar, "ProgressBar"); getWidget(mProgressBar, "ProgressBar");
getWidget(mBackgroundImage, "BackgroundImage");
mProgressBar->setScrollViewPage(1); mProgressBar->setScrollViewPage(1);
mBackgroundMaterial = Ogre::MaterialManager::getSingleton().create("BackgroundMaterial", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); mBackgroundImage = MyGUI::Gui::getInstance().createWidgetReal<BackgroundImage>("ImageBox", 0,0,1,1,
mBackgroundMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); MyGUI::Align::Stretch, "Menu");
mBackgroundMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false);
mBackgroundMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); setVisible(false);
mRectangle = new Ogre::Rectangle2D(true);
mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0);
mRectangle->setMaterial("BackgroundMaterial");
// Render the background before everything else
mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY-1);
// Use infinite AAB to always stay visible
Ogre::AxisAlignedBox aabInf;
aabInf.setInfinite();
mRectangle->setBoundingBox(aabInf);
// Attach background to the scene
Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
node->attachObject(mRectangle);
mRectangle->setVisible(false);
} }
void LoadingScreen::setLabel(const std::string &label) void LoadingScreen::setLabel(const std::string &label)
@ -63,18 +48,25 @@ namespace MWGui
LoadingScreen::~LoadingScreen() LoadingScreen::~LoadingScreen()
{ {
delete mRectangle; }
void LoadingScreen::setVisible(bool visible)
{
WindowBase::setVisible(visible);
mBackgroundImage->setVisible(visible);
} }
void LoadingScreen::onResChange(int w, int h) void LoadingScreen::onResChange(int w, int h)
{ {
setCoord(0,0,w,h); setCoord(0,0,w,h);
mBackgroundImage->setCoord(MyGUI::IntCoord(0,0,w,h));
} }
void LoadingScreen::loadingOn() void LoadingScreen::loadingOn()
{ {
// Early-out if already on // Early-out if already on
if (mRectangle->getVisible()) if (mMainWidget->getVisible())
return; return;
// Temporarily turn off VSync, we want to do actual loading rather than waiting for the screen to sync. // Temporarily turn off VSync, we want to do actual loading rather than waiting for the screen to sync.
@ -106,7 +98,7 @@ namespace MWGui
texture->createInternalResources(); texture->createInternalResources();
mWindow->copyContentsToMemory(texture->getBuffer()->lock(Ogre::Image::Box(0,0,width,height), Ogre::HardwareBuffer::HBL_DISCARD)); mWindow->copyContentsToMemory(texture->getBuffer()->lock(Ogre::Image::Box(0,0,width,height), Ogre::HardwareBuffer::HBL_DISCARD));
texture->getBuffer()->unlock(); texture->getBuffer()->unlock();
mBackgroundImage->setImageTexture(texture->getName()); mBackgroundImage->setBackgroundImage(texture->getName(), false, false);
} }
setVisible(true); setVisible(true);
@ -149,9 +141,10 @@ namespace MWGui
{ {
std::string const & randomSplash = mResources.at (rand() % mResources.size()); std::string const & randomSplash = mResources.at (rand() % mResources.size());
Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME);
mBackgroundImage->setImageTexture (randomSplash); // TODO: add option (filename pattern?) to use image aspect ratio instead of 4:3
mBackgroundImage->setBackgroundImage(randomSplash, true, true);
} }
else else
std::cerr << "No loading screens found!" << std::endl; std::cerr << "No loading screens found!" << std::endl;
@ -237,8 +230,6 @@ namespace MWGui
mWindow->update(false); mWindow->update(false);
mRectangle->setVisible(false);
// resume 3d rendering // resume 3d rendering
mSceneMgr->clearSpecialCaseRenderQueues(); mSceneMgr->clearSpecialCaseRenderQueues();
mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE);

@ -10,6 +10,8 @@
namespace MWGui namespace MWGui
{ {
class BackgroundImage;
class LoadingScreen : public WindowBase, public Loading::Listener class LoadingScreen : public WindowBase, public Loading::Listener
{ {
public: public:
@ -25,6 +27,8 @@ namespace MWGui
virtual void setProgress (size_t value); virtual void setProgress (size_t value);
virtual void increaseProgress (size_t increase); virtual void increaseProgress (size_t increase);
virtual void setVisible(bool visible);
virtual void removeWallpaper(); virtual void removeWallpaper();
LoadingScreen(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* rw); LoadingScreen(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* rw);
@ -51,10 +55,7 @@ namespace MWGui
MyGUI::TextBox* mLoadingText; MyGUI::TextBox* mLoadingText;
MyGUI::ScrollBar* mProgressBar; MyGUI::ScrollBar* mProgressBar;
MyGUI::ImageBox* mBackgroundImage; BackgroundImage* mBackgroundImage;
Ogre::Rectangle2D* mRectangle;
Ogre::MaterialPtr mBackgroundMaterial;
Ogre::StringVector mResources; Ogre::StringVector mResources;

@ -14,6 +14,8 @@
#include "savegamedialog.hpp" #include "savegamedialog.hpp"
#include "confirmationdialog.hpp" #include "confirmationdialog.hpp"
#include "imagebutton.hpp"
#include "backgroundimage.hpp"
namespace MWGui namespace MWGui
{ {
@ -132,34 +134,14 @@ namespace MWGui
void MainMenu::showBackground(bool show) void MainMenu::showBackground(bool show)
{ {
if (mBackground) if (show && !mBackground)
{
MyGUI::Gui::getInstance().destroyWidget(mBackground);
mBackground = NULL;
}
if (show)
{ {
if (!mBackground) mBackground = MyGUI::Gui::getInstance().createWidgetReal<BackgroundImage>("ImageBox", 0,0,1,1,
{ MyGUI::Align::Stretch, "Menu");
mBackground = MyGUI::Gui::getInstance().createWidgetReal<MyGUI::ImageBox>("ImageBox", 0,0,1,1, mBackground->setBackgroundImage("textures\\menu_morrowind.dds");
MyGUI::Align::Stretch, "Menu");
mBackground->setImageTexture("black.png");
// Use black bars to correct aspect ratio. The video player also does it, so we need to do it
// for mw_logo.bik to align correctly with menu_morrowind.dds.
MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize();
// No way to un-hardcode this right now, menu_morrowind.dds is 1024x512 but was designed for 4:3
double imageaspect = 4.0/3.0;
int leftPadding = std::max(0.0, (screenSize.width - screenSize.height * imageaspect) / 2);
int topPadding = std::max(0.0, (screenSize.height - screenSize.width / imageaspect) / 2);
MyGUI::ImageBox* image = mBackground->createWidget<MyGUI::ImageBox>("ImageBox",
leftPadding, topPadding, screenSize.width - leftPadding*2, screenSize.height - topPadding*2, MyGUI::Align::Default);
image->setImageTexture("textures\\menu_morrowind.dds");
}
} }
if (mBackground)
mBackground->setVisible(show);
} }
void MainMenu::updateMenu() void MainMenu::updateMenu()
@ -174,6 +156,7 @@ namespace MWGui
MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState();
showBackground(state == MWBase::StateManager::State_NoGame); showBackground(state == MWBase::StateManager::State_NoGame);
mVersionText->setVisible(state == MWBase::StateManager::State_NoGame);
std::vector<std::string> buttons; std::vector<std::string> buttons;

@ -3,11 +3,11 @@
#include <openengine/gui/layout.hpp> #include <openengine/gui/layout.hpp>
#include "imagebutton.hpp"
namespace MWGui namespace MWGui
{ {
class ImageButton;
class BackgroundImage;
class SaveGameDialog; class SaveGameDialog;
class MainMenu : public OEngine::GUI::Layout class MainMenu : public OEngine::GUI::Layout
@ -29,7 +29,7 @@ namespace MWGui
MyGUI::Widget* mButtonBox; MyGUI::Widget* mButtonBox;
MyGUI::TextBox* mVersionText; MyGUI::TextBox* mVersionText;
MyGUI::ImageBox* mBackground; BackgroundImage* mBackground;
std::map<std::string, MWGui::ImageButton*> mButtons; std::map<std::string, MWGui::ImageButton*> mButtons;

@ -80,6 +80,8 @@ namespace MWGui
mCharacterSelection->removeAllItems(); mCharacterSelection->removeAllItems();
int selectedIndex = MyGUI::ITEM_NONE;
for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it) for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it)
{ {
if (it->begin()!=it->end()) if (it->begin()!=it->end())
@ -109,11 +111,13 @@ namespace MWGui
it->begin()->mPath.parent_path().filename().string()))) it->begin()->mPath.parent_path().filename().string())))
{ {
mCurrentCharacter = &*it; mCurrentCharacter = &*it;
mCharacterSelection->setIndexSelected(mCharacterSelection->getItemCount()-1); selectedIndex = mCharacterSelection->getItemCount()-1;
} }
} }
} }
mCharacterSelection->setIndexSelected(selectedIndex);
fillSaveList(); fillSaveList();
} }
@ -259,7 +263,7 @@ namespace MWGui
timeinfo = localtime(&time); timeinfo = localtime(&time);
// Use system/environment locale settings for datetime formatting // Use system/environment locale settings for datetime formatting
std::setlocale(LC_TIME, ""); setlocale(LC_TIME, "");
const int size=1024; const int size=1024;
char buffer[size]; char buffer[size];

@ -360,7 +360,8 @@ namespace MWGui
if (mCurrentBalance != 0) if (mCurrentBalance != 0)
{ {
addOrRemoveGold(mCurrentBalance, player); addOrRemoveGold(mCurrentBalance, player);
addOrRemoveGold(-mCurrentBalance, mPtr); mPtr.getClass().getCreatureStats(mPtr).setGoldPool(
mPtr.getClass().getCreatureStats(mPtr).getGoldPool() - mCurrentBalance );
} }
updateTradeTime(); updateTradeTime();
@ -470,28 +471,20 @@ namespace MWGui
int TradeWindow::getMerchantGold() int TradeWindow::getMerchantGold()
{ {
int merchantGold = 0; int merchantGold = mPtr.getClass().getCreatureStats(mPtr).getGoldPool();
MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr);
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{
if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, MWWorld::ContainerStore::sGoldId))
merchantGold += it->getRefData().getCount();
}
return merchantGold; return merchantGold;
} }
// Relates to NPC gold reset delay // Relates to NPC gold reset delay
void TradeWindow::checkTradeTime() void TradeWindow::checkTradeTime()
{ {
MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr); MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr);
const MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr);
double delay = boost::lexical_cast<double>(MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fBarterGoldResetDelay")->getInt()); double delay = boost::lexical_cast<double>(MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fBarterGoldResetDelay")->getInt());
// if time stamp longer than gold reset delay, reset gold. // if time stamp longer than gold reset delay, reset gold.
if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getTradeTime() + delay) if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getTradeTime() + delay)
{ {
addOrRemoveGold(-store.count(MWWorld::ContainerStore::sGoldId), mPtr); sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr));
addOrRemoveGold(+sellerStats.getGoldPool(), mPtr);
} }
} }

@ -61,6 +61,7 @@
#include "itemview.hpp" #include "itemview.hpp"
#include "fontloader.hpp" #include "fontloader.hpp"
#include "videowidget.hpp" #include "videowidget.hpp"
#include "backgroundimage.hpp"
namespace MWGui namespace MWGui
{ {
@ -160,6 +161,7 @@ namespace MWGui
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWScrollView>("Widget"); MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWScrollView>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWScrollBar>("Widget"); MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWScrollBar>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<VideoWidget>("Widget"); MyGUI::FactoryManager::getInstance().registerFactory<VideoWidget>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<BackgroundImage>("Widget");
BookPage::registerMyGUIComponents (); BookPage::registerMyGUIComponents ();
ItemView::registerComponents(); ItemView::registerComponents();

@ -29,6 +29,7 @@
#include "aicombat.hpp" #include "aicombat.hpp"
#include "aifollow.hpp" #include "aifollow.hpp"
#include "aipersue.hpp"
namespace namespace
{ {
@ -175,14 +176,16 @@ namespace MWMechanics
adjustMagicEffects (ptr); adjustMagicEffects (ptr);
if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats())
calculateDynamicStats (ptr); calculateDynamicStats (ptr);
calculateCreatureStatModifiers (ptr, duration); calculateCreatureStatModifiers (ptr, duration);
// AI // AI
if(MWBase::Environment::get().getMechanicsManager()->isAIActive()) if(MWBase::Environment::get().getMechanicsManager()->isAIActive())
{ {
CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr);
//engage combat or not?
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
//engage combat or not?
if(ptr != player && !creatureStats.isHostile()) if(ptr != player && !creatureStats.isHostile())
{ {
ESM::Position playerpos = player.getRefData().getPosition(); ESM::Position playerpos = player.getRefData().getPosition();
@ -196,9 +199,8 @@ namespace MWMechanics
{ {
disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr);
} }
bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr); if( (fight == 100 )
if( ( (fight == 100 )
|| (fight >= 95 && d <= 3000) || (fight >= 95 && d <= 3000)
|| (fight >= 90 && d <= 2000) || (fight >= 90 && d <= 2000)
|| (fight >= 80 && d <= 1000) || (fight >= 80 && d <= 1000)
@ -206,15 +208,20 @@ namespace MWMechanics
|| (fight >= 70 && disp <= 35 && d <= 1000) || (fight >= 70 && disp <= 35 && d <= 1000)
|| (fight >= 60 && disp <= 30 && d <= 1000) || (fight >= 60 && disp <= 30 && d <= 1000)
|| (fight >= 50 && disp == 0) || (fight >= 50 && disp == 0)
|| (fight >= 40 && disp <= 10 && d <= 500) ) || (fight >= 40 && disp <= 10 && d <= 500)
&& LOS
) )
{ {
creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr())); bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player)
creatureStats.setHostile(true); && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr);
if (LOS)
{
creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr()));
creatureStats.setHostile(true);
}
} }
} }
updateCrimePersuit(ptr, duration);
creatureStats.getAiSequence().execute (ptr,duration); creatureStats.getAiSequence().execute (ptr,duration);
} }
@ -539,6 +546,8 @@ namespace MWMechanics
ref.getPtr().getCellRef().mPos = ipos; ref.getPtr().getCellRef().mPos = ipos;
// TODO: Add AI to follow player and fight for him // TODO: Add AI to follow player and fight for him
AiFollow package(ptr.getRefData().getHandle());
MWWorld::Class::get (ref.getPtr()).getCreatureStats (ref.getPtr()).getAiSequence().stack(package);
// TODO: VFX_SummonStart, VFX_SummonEnd // TODO: VFX_SummonStart, VFX_SummonEnd
creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, creatureStats.mSummonedCreatures.insert(std::make_pair(it->first,
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos).getRefData().getHandle())); MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos).getRefData().getHandle()));
@ -711,6 +720,72 @@ namespace MWMechanics
} }
} }
void Actors::updateCrimePersuit(const MWWorld::Ptr& ptr, float duration)
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (ptr != player && ptr.getClass().isNpc())
{
// get stats of witness
CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr);
NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats(ptr);
// If I'm a guard and I'm not hostile
if (ptr.getClass().isClass(ptr, "Guard") && !creatureStats.isHostile())
{
/// \todo Move me! I shouldn't be here...
const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
float cutoff = float(esmStore.get<ESM::GameSetting>().find("iCrimeThreshold")->getInt()) *
float(esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->getInt()) *
esmStore.get<ESM::GameSetting>().find("fCrimeGoldDiscountMult")->getFloat();
// Attack on sight if bounty is greater than the cutoff
if ( player.getClass().getNpcStats(player).getBounty() >= cutoff
&& MWBase::Environment::get().getWorld()->getLOS(ptr, player)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr))
{
creatureStats.getAiSequence().stack(AiCombat(player));
creatureStats.setHostile(true);
npcStats.setCrimeId( MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() );
}
}
// if I was a witness to a crime
if (npcStats.getCrimeId() != -1)
{
// if you've payed for your crimes and I havent noticed
if( npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() )
{
// Calm witness down
if (ptr.getClass().isClass(ptr, "Guard"))
creatureStats.getAiSequence().stopPersue();
creatureStats.getAiSequence().stopCombat();
// Reset factors to attack
// TODO: Not a complete list, disposition changes?
creatureStats.setHostile(false);
creatureStats.setAttacked(false);
// Update witness crime id
npcStats.setCrimeId(-1);
}
else if (!creatureStats.isHostile())
{
if (ptr.getClass().isClass(ptr, "Guard"))
creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player)));
else
creatureStats.getAiSequence().stack(AiCombat(player));
creatureStats.setHostile(true);
}
}
// if I didn't report a crime was I attacked?
else if (creatureStats.getAttacked() && !creatureStats.isHostile())
{
creatureStats.getAiSequence().stack(AiCombat(player));
creatureStats.setHostile(true);
}
}
}
Actors::Actors() {} Actors::Actors() {}
Actors::~Actors() Actors::~Actors()

@ -42,6 +42,8 @@ namespace MWMechanics
void updateEquippedLight (const MWWorld::Ptr& ptr, float duration); void updateEquippedLight (const MWWorld::Ptr& ptr, float duration);
void updateCrimePersuit (const MWWorld::Ptr& ptr, float duration);
public: public:
Actors(); Actors();

@ -49,7 +49,9 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration)
} }
} }
MWWorld::Ptr target = world->getPtr(mObjectId,false); MWWorld::Ptr target = world->searchPtr(mObjectId,false);
if(target == MWWorld::Ptr()) return true;
ESM::Position targetPos = target.getRefData().getPosition(); ESM::Position targetPos = target.getRefData().getPosition();
bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY;

@ -1,24 +1,24 @@
#ifndef GAME_MWMECHANICS_AIACTIVATE_H #ifndef GAME_MWMECHANICS_AIACTIVATE_H
#define GAME_MWMECHANICS_AIACTIVATE_H #define GAME_MWMECHANICS_AIACTIVATE_H
#include "aipackage.hpp" #include "aipackage.hpp"
#include <string> #include <string>
#include "pathfinding.hpp" #include "pathfinding.hpp"
namespace MWMechanics namespace MWMechanics
{ {
class AiActivate : public AiPackage class AiActivate : public AiPackage
{ {
public: public:
AiActivate(const std::string &objectId); AiActivate(const std::string &objectId);
virtual AiActivate *clone() const; virtual AiActivate *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed? ///< \return Package completed?
virtual int getTypeId() const; virtual int getTypeId() const;
private: private:
std::string mObjectId; std::string mObjectId;
PathFinder mPathFinder; PathFinder mPathFinder;

@ -1,28 +1,34 @@
#include "aifollow.hpp" #include "aifollow.hpp"
#include <iostream>
#include <OgreMath.h>
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "movement.hpp" #include "movement.hpp"
#include <OgreMath.h>
#include "steering.hpp" #include "steering.hpp"
MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z)
: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) : mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0)
{ {
} }
MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z)
: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0) : mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0)
{
}
MWMechanics::AiFollow::AiFollow(const std::string &actorId)
: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0)
{ {
} }
bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
{ {
const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false);
if(target == MWWorld::Ptr()) return true;
mTimer = mTimer + duration; mTimer = mTimer + duration;
mStuckTimer = mStuckTimer + duration; mStuckTimer = mStuckTimer + duration;
@ -30,22 +36,25 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
ESM::Position pos = actor.getRefData().getPosition(); ESM::Position pos = actor.getRefData().getPosition();
if(mTotalTime > mDuration && mDuration != 0) if(!mAlwaysFollow)
return true;
if((pos.pos[0]-mX)*(pos.pos[0]-mX) +
(pos.pos[1]-mY)*(pos.pos[1]-mY) +
(pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100)
{ {
if(actor.getCell()->isExterior()) if(mTotalTime > mDuration && mDuration != 0)
{ return true;
if(mCellId == "")
return true; if((pos.pos[0]-mX)*(pos.pos[0]-mX) +
} (pos.pos[1]-mY)*(pos.pos[1]-mY) +
else (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100)
{ {
if(mCellId == actor.getCell()->getCell()->mName) if(actor.getCell()->isExterior())
return true; {
if(mCellId == "")
return true;
}
else
{
if(mCellId == actor.getCell()->getCell()->mName)
return true;
}
} }
} }
@ -69,7 +78,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
{ {
ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back();
if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX) if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX)
+(dest.mY - lastPos.mY)*(dest.mY - lastPos.mY) +(dest.mY - lastPos.mY)*(dest.mY - lastPos.mY)
+(dest.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ) +(dest.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ)
> 100*100) > 100*100)

@ -14,6 +14,7 @@ namespace MWMechanics
public: public:
AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z);
AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z);
AiFollow(const std::string &ActorId);
virtual AiFollow *clone() const; virtual AiFollow *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed? ///< \return Package completed?
@ -22,6 +23,7 @@ namespace MWMechanics
std::string getFollowedActor(); std::string getFollowedActor();
private: private:
bool mAlwaysFollow; //this will make the actor always follow, thus ignoring mDuration and mX,mY,mZ (used for summoned creatures).
float mDuration; float mDuration;
float mX; float mX;
float mY; float mY;

@ -19,7 +19,8 @@ namespace MWMechanics
TypeIdEscort = 2, TypeIdEscort = 2,
TypeIdFollow = 3, TypeIdFollow = 3,
TypeIdActivate = 4, TypeIdActivate = 4,
TypeIdCombat = 5 TypeIdCombat = 5,
TypeIdPersue = 6
}; };
virtual ~AiPackage(); virtual ~AiPackage();

@ -0,0 +1,106 @@
#include "aipersue.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/action.hpp"
#include "../mwworld/cellstore.hpp"
#include "steering.hpp"
#include "movement.hpp"
#include "creaturestats.hpp"
MWMechanics::AiPersue::AiPersue(const std::string &objectId)
: mObjectId(objectId)
{
}
MWMechanics::AiPersue *MWMechanics::AiPersue::clone() const
{
return new AiPersue(*this);
}
bool MWMechanics::AiPersue::execute (const MWWorld::Ptr& actor, float duration)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
ESM::Position pos = actor.getRefData().getPosition();
Movement &movement = actor.getClass().getMovementSettings(actor);
const ESM::Cell *cell = actor.getCell()->getCell();
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
MWWorld::Ptr player = world->getPlayerPtr();
if(cell->mData.mX != player.getCell()->getCell()->mData.mX)
{
int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) >
sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
{
movement.mPosition[1] = 0;
return false;
}
}
if(cell->mData.mY != player.getCell()->getCell()->mData.mY)
{
int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) >
sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
{
movement.mPosition[1] = 0;
return false;
}
}
MWWorld::Ptr target = world->getPtr(mObjectId,false);
ESM::Position targetPos = target.getRefData().getPosition();
bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY;
if(!mPathFinder.isPathConstructed() || cellChange)
{
mCellX = cell->mData.mX;
mCellY = cell->mData.mY;
ESM::Pathgrid::Point dest;
dest.mX = targetPos.pos[0];
dest.mY = targetPos.pos[1];
dest.mZ = targetPos.pos[2];
ESM::Pathgrid::Point start;
start.mX = pos.pos[0];
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
mPathFinder.buildPath(start, dest, actor.getCell(), true);
}
if((pos.pos[0]-targetPos.pos[0])*(pos.pos[0]-targetPos.pos[0])+
(pos.pos[1]-targetPos.pos[1])*(pos.pos[1]-targetPos.pos[1])+
(pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 200*200)
{
movement.mPosition[1] = 0;
MWWorld::Ptr target = world->getPtr(mObjectId,false);
MWWorld::Class::get(target).activate(target,actor).get()->execute(actor);
return true;
}
if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
{
movement.mPosition[1] = 0;
MWWorld::Ptr target = world->getPtr(mObjectId,false);
MWWorld::Class::get(target).activate(target,actor).get()->execute(actor);
return true;
}
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
zTurn(actor, Ogre::Degree(zAngle));
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
movement.mPosition[1] = 1;
return false;
}
int MWMechanics::AiPersue::getTypeId() const
{
return TypeIdPersue;
}

@ -0,0 +1,29 @@
#ifndef GAME_MWMECHANICS_AIPERSUE_H
#define GAME_MWMECHANICS_AIPERSUE_H
#include "aipackage.hpp"
#include <string>
#include "pathfinding.hpp"
namespace MWMechanics
{
class AiPersue : public AiPackage
{
public:
AiPersue(const std::string &objectId);
virtual AiPersue *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
private:
std::string mObjectId;
PathFinder mPathFinder;
int mCellX;
int mCellY;
};
}
#endif

@ -73,6 +73,15 @@ void MWMechanics::AiSequence::stopCombat()
} }
} }
void MWMechanics::AiSequence::stopPersue()
{
while (getTypeId() == AiPackage::TypeIdPersue)
{
delete *mPackages.begin();
mPackages.erase (mPackages.begin());
}
}
bool MWMechanics::AiSequence::isPackageDone() const bool MWMechanics::AiSequence::isPackageDone() const
{ {
return mDone; return mDone;

@ -49,6 +49,9 @@ namespace MWMechanics
void stopCombat(); void stopCombat();
///< Removes all combat packages until first non-combat or stack empty. ///< Removes all combat packages until first non-combat or stack empty.
void stopPersue();
///< Removes all persue packages until first non-persue or stack empty.
bool isPackageDone() const; bool isPackageDone() const;
///< Has a package been completed during the last update? ///< Has a package been completed during the last update?

@ -18,9 +18,10 @@
namespace MWMechanics namespace MWMechanics
{ {
// NOTE: determined empirically but probably need further tweaking // NOTE: determined empirically but probably need further tweaking
static const int COUNT_BEFORE_STUCK = 20;
static const int COUNT_BEFORE_RESET = 200; static const int COUNT_BEFORE_RESET = 200;
static const int COUNT_EVADE = 7; static const float DIST_SAME_SPOT = 1.8f;
static const float DURATION_SAME_SPOT = 1.0f;
static const float DURATION_TO_EVADE = 0.4f;
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<int>& idle, bool repeat): AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<int>& idle, bool repeat):
mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat)
@ -35,7 +36,8 @@ namespace MWMechanics
, mPrevY(0) , mPrevY(0)
, mWalkState(State_Norm) , mWalkState(State_Norm)
, mStuckCount(0) , mStuckCount(0)
, mEvadeCount(0) , mEvadeDuration(0)
, mStuckDuration(0)
, mSaidGreeting(false) , mSaidGreeting(false)
{ {
for(unsigned short counter = 0; counter < mIdle.size(); counter++) for(unsigned short counter = 0; counter < mIdle.size(); counter++)
@ -69,6 +71,7 @@ namespace MWMechanics
return new AiWander(*this); return new AiWander(*this);
} }
// TODO: duration is passed in but never used, check if it is needed
bool AiWander::execute (const MWWorld::Ptr& actor,float duration) bool AiWander::execute (const MWWorld::Ptr& actor,float duration)
{ {
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
@ -102,20 +105,21 @@ namespace MWMechanics
ESM::Position pos = actor.getRefData().getPosition(); ESM::Position pos = actor.getRefData().getPosition();
// Once off initialization to discover & store allowed node points for this actor.
if(!mStoredAvailableNodes) if(!mStoredAvailableNodes)
{ {
mStoredAvailableNodes = true;
mPathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell()); mPathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
mCellX = actor.getCell()->getCell()->mData.mX; mCellX = actor.getCell()->getCell()->mData.mX;
mCellY = actor.getCell()->getCell()->mData.mY; mCellY = actor.getCell()->getCell()->mData.mY;
// TODO: If there is no path does this actor get stuck forever?
if(!mPathgrid) if(!mPathgrid)
mDistance = 0; mDistance = 0;
else if(mPathgrid->mPoints.empty()) else if(mPathgrid->mPoints.empty())
mDistance = 0; mDistance = 0;
if(mDistance) if(mDistance) // A distance value is initially passed into the constructor.
{ {
mXCell = 0; mXCell = 0;
mYCell = 0; mYCell = 0;
@ -125,14 +129,18 @@ namespace MWMechanics
mYCell = mCellY * ESM::Land::REAL_SIZE; mYCell = mCellY * ESM::Land::REAL_SIZE;
} }
// convert npcPos to local (i.e. cell) co-ordinates
Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos); Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos);
npcPos[0] = npcPos[0] - mXCell; npcPos[0] = npcPos[0] - mXCell;
npcPos[1] = npcPos[1] - mYCell; npcPos[1] = npcPos[1] - mYCell;
// populate mAllowedNodes for this actor with pathgrid point indexes based on mDistance
// NOTE: mPoints and mAllowedNodes contain points in local co-ordinates
for(unsigned int counter = 0; counter < mPathgrid->mPoints.size(); counter++) for(unsigned int counter = 0; counter < mPathgrid->mPoints.size(); counter++)
{ {
Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, mPathgrid->mPoints[counter].mY, Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX,
mPathgrid->mPoints[counter].mZ); mPathgrid->mPoints[counter].mY,
mPathgrid->mPoints[counter].mZ);
if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance) if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance)
mAllowedNodes.push_back(mPathgrid->mPoints[counter]); mAllowedNodes.push_back(mPathgrid->mPoints[counter]);
} }
@ -143,18 +151,22 @@ namespace MWMechanics
unsigned int index = 0; unsigned int index = 0;
for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++)
{ {
Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY, Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX,
mAllowedNodes[counterThree].mZ); mAllowedNodes[counterThree].mY,
mAllowedNodes[counterThree].mZ);
float tempDist = npcPos.squaredDistance(nodePos); float tempDist = npcPos.squaredDistance(nodePos);
if(tempDist < closestNode) if(tempDist < closestNode)
index = counterThree; index = counterThree;
} }
mCurrentNode = mAllowedNodes[index]; mCurrentNode = mAllowedNodes[index];
mAllowedNodes.erase(mAllowedNodes.begin() + index); mAllowedNodes.erase(mAllowedNodes.begin() + index);
mStoredAvailableNodes = true; // set only if successful in finding allowed nodes
} }
} }
} }
// TODO: Does this actor stay in one spot forever while in AiWander?
if(mAllowedNodes.empty()) if(mAllowedNodes.empty())
mDistance = 0; mDistance = 0;
@ -162,7 +174,7 @@ namespace MWMechanics
if(mDistance && (mCellX != actor.getCell()->getCell()->mData.mX || mCellY != actor.getCell()->getCell()->mData.mY)) if(mDistance && (mCellX != actor.getCell()->getCell()->mData.mX || mCellY != actor.getCell()->getCell()->mData.mY))
mDistance = 0; mDistance = 0;
if(mChooseAction) if(mChooseAction) // Initially set true by the constructor.
{ {
mPlayedIdle = 0; mPlayedIdle = 0;
unsigned short idleRoll = 0; unsigned short idleRoll = 0;
@ -208,7 +220,8 @@ namespace MWMechanics
} }
} }
if(mIdleNow) // Allow interrupting a walking actor to trigger a greeting
if(mIdleNow || (mWalking && (mWalkState != State_Norm)))
{ {
// Play a random voice greeting if the player gets too close // Play a random voice greeting if the player gets too close
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
@ -222,6 +235,14 @@ namespace MWMechanics
float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance( float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance(
Ogre::Vector3(actor.getRefData().getPosition().pos)); Ogre::Vector3(actor.getRefData().getPosition().pos));
if(mWalking && playerDist <= helloDistance)
{
stopWalking(actor);
mMoveNow = false;
mWalking = false;
mWalkState = State_Norm;
}
if (!mSaidGreeting) if (!mSaidGreeting)
{ {
// TODO: check if actor is aware / has line of sight // TODO: check if actor is aware / has line of sight
@ -263,16 +284,24 @@ namespace MWMechanics
dest.mY = destNodePos[1] + mYCell; dest.mY = destNodePos[1] + mYCell;
dest.mZ = destNodePos[2]; dest.mZ = destNodePos[2];
// actor position is already in world co-ordinates
ESM::Pathgrid::Point start; ESM::Pathgrid::Point start;
start.mX = pos.pos[0]; start.mX = pos.pos[0];
start.mY = pos.pos[1]; start.mY = pos.pos[1];
start.mZ = pos.pos[2]; start.mZ = pos.pos[2];
// don't take shortcuts for wandering
mPathFinder.buildPath(start, dest, actor.getCell(), false); mPathFinder.buildPath(start, dest, actor.getCell(), false);
if(mPathFinder.isPathConstructed()) if(mPathFinder.isPathConstructed())
{ {
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node): // buildPath inserts dest in case it is not a pathgraph point index
// which is a duplicate for AiWander
//if(mPathFinder.getPathSize() > 1)
//mPathFinder.getPath().pop_back();
// Remove this node as an option and add back the previously used node
// (stops NPC from picking the same node):
ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; ESM::Pathgrid::Point temp = mAllowedNodes[randNode];
mAllowedNodes.erase(mAllowedNodes.begin() + randNode); mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
mAllowedNodes.push_back(mCurrentNode); mAllowedNodes.push_back(mCurrentNode);
@ -298,15 +327,21 @@ namespace MWMechanics
} }
else else
{ {
/* 1 n /* f t
* State_Norm <---> State_CheckStuck --> State_Evade * State_Norm <---> State_CheckStuck --> State_Evade
* ^ ^ | ^ | ^ | | * ^ ^ | ^ | ^ | |
* | | | | | | | | * | | | | | | | |
* | +---+ +---+ +---+ | m * | +---+ +---+ +---+ | u
* | any < n < m | * | any < t < u |
* +--------------------------------------------+ * +--------------------------------------------+
*
* f = one frame
* t = how long before considered stuck
* u = how long to move sideways
*/ */
bool samePosition = (abs(pos.pos[0] - mPrevX) < 1) && (abs(pos.pos[1] - mPrevY) < 1); bool samePosition = (abs(pos.pos[0] - mPrevX) < DIST_SAME_SPOT) &&
(abs(pos.pos[1] - mPrevY) < DIST_SAME_SPOT);
switch(mWalkState) switch(mWalkState)
{ {
case State_Norm: case State_Norm:
@ -322,30 +357,33 @@ namespace MWMechanics
if(!samePosition) if(!samePosition)
{ {
mWalkState = State_Norm; mWalkState = State_Norm;
// to do this properly need yet another variable, simply don't clear for now mStuckDuration = 0;
//mStuckCount = 0;
break; break;
} }
else else
{ {
// consider stuck only if position unchanges consecutively mStuckDuration += duration;
if((mStuckCount++ % COUNT_BEFORE_STUCK) == 0) // consider stuck only if position unchanges for a period
if(mStuckDuration > DURATION_SAME_SPOT)
{
mWalkState = State_Evade; mWalkState = State_Evade;
// NOTE: mStuckCount is purposely not cleared here mStuckDuration = 0;
mStuckCount++;
}
else else
break; // still in the same state, but counter got incremented break; // still in the same state, but duration added to timer
} }
} }
/* FALL THROUGH */ /* FALL THROUGH */
case State_Evade: case State_Evade:
{ {
if(mEvadeCount++ < COUNT_EVADE) mEvadeDuration += duration;
if(mEvadeDuration < DURATION_TO_EVADE)
break; break;
else else
{ {
mWalkState = State_Norm; // tried to evade, assume all is ok and start again mWalkState = State_Norm; // tried to evade, assume all is ok and start again
// NOTE: mStuckCount is purposely not cleared here mEvadeDuration = 0;
mEvadeCount = 0;
} }
} }
/* NO DEFAULT CASE */ /* NO DEFAULT CASE */
@ -353,7 +391,7 @@ namespace MWMechanics
if(mWalkState == State_Evade) if(mWalkState == State_Evade)
{ {
//std::cout << "Stuck \""<<actor.getClass().getName(actor)<<"\"" << std::endl; //std::cout << "Stuck \""<<actor.getClass().getName(actor)<<"\"" << std::endl;
// diagonal should have same animation as walk forward // diagonal should have same animation as walk forward
actor.getClass().getMovementSettings(actor).mPosition[0] = 1; actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
@ -363,13 +401,16 @@ namespace MWMechanics
} }
else else
{ {
// normal walk forward
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
// turn towards the next point in mPath
// TODO: possibly no need to check every frame, maybe every 30 should be ok?
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
} }
if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset
{ {
//std::cout << "Reset \""<<actor.getClass().getName(actor)<<"\"" << std::endl; //std::cout << "Reset \""<<actor.getClass().getName(actor)<<"\"" << std::endl;
mWalkState = State_Norm; mWalkState = State_Norm;
mStuckCount = 0; mStuckCount = 0;

@ -38,8 +38,10 @@ namespace MWMechanics
float mY; float mY;
float mZ; float mZ;
// Cell location
int mCellX; int mCellX;
int mCellY; int mCellY;
// Cell location multiplied by ESM::Land::REAL_SIZE
float mXCell; float mXCell;
float mYCell; float mYCell;
@ -56,7 +58,8 @@ namespace MWMechanics
WalkState mWalkState; WalkState mWalkState;
int mStuckCount; int mStuckCount;
int mEvadeCount; float mStuckDuration;
float mEvadeDuration;
bool mStoredAvailableNodes; bool mStoredAvailableNodes;
bool mChooseAction; bool mChooseAction;

@ -435,8 +435,9 @@ namespace MWMechanics
return getMovementFlag (Flag_Run) || getMovementFlag (Flag_ForceRun); return getMovementFlag (Flag_Run) || getMovementFlag (Flag_ForceRun);
case Stance_Sneak: case Stance_Sneak:
return getMovementFlag (Flag_Sneak) || getMovementFlag (Flag_ForceSneak); return getMovementFlag (Flag_Sneak) || getMovementFlag (Flag_ForceSneak);
default:
return false;
} }
return false; // shut up, compiler
} }
DrawState_ CreatureStats::getDrawState() const DrawState_ CreatureStats::getDrawState() const

@ -175,15 +175,12 @@ namespace MWMechanics
void talkedToPlayer(); void talkedToPlayer();
bool isAlarmed() const; bool isAlarmed() const;
void setAlarmed (bool alarmed); void setAlarmed (bool alarmed);
bool getAttacked() const; bool getAttacked() const;
void setAttacked (bool attacked); void setAttacked (bool attacked);
bool isHostile() const; bool isHostile() const;
void setHostile (bool hostile); void setHostile (bool hostile);
bool getCreatureTargetted() const; bool getCreatureTargetted() const;

@ -1,5 +1,6 @@
#include "mechanicsmanagerimp.hpp" #include "mechanicsmanagerimp.hpp"
#include "npcstats.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
@ -802,34 +803,74 @@ namespace MWMechanics
bool MechanicsManager::commitCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) bool MechanicsManager::commitCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg)
{ {
if (ptr.getRefData().getHandle() != "player") // NOTE: int arg can be from itemTaken() so DON'T modify it, since it is
// passed to reportCrime later on in this function.
// Only player can commit crime and no victimless crimes
if (ptr.getRefData().getHandle() != "player" || victim.isEmpty())
return false; return false;
bool reported=false; const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
for (Actors::PtrControllerMap::const_iterator it = mActors.begin(); it != mActors.end(); ++it)
{ // What amount of alarm did this crime generate?
if (it->first != ptr && int alarm;
MWBase::Environment::get().getWorld()->getLOS(ptr, it->first) && if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
awarenessCheck(ptr, it->first)) alarm = esmStore.get<ESM::GameSetting>().find("iAlarmTresspass")->getInt();
else if (type == OT_Pickpocket)
alarm = esmStore.get<ESM::GameSetting>().find("iAlarmPickPocket")->getInt();
else if (type == OT_Assault)
alarm = esmStore.get<ESM::GameSetting>().find("iAlarmAttack")->getInt();
else if (type == OT_Murder)
alarm = esmStore.get<ESM::GameSetting>().find("iAlarmKilling")->getInt();
else if (type == OT_Theft)
alarm = esmStore.get<ESM::GameSetting>().find("iAlarmStealing")->getInt();
// Innocent until proven guilty
bool reported = false;
// Find all the NPCs within the alarm radius
std::vector<MWWorld::Ptr> neighbors;
mActors.getObjectsInRange(Ogre::Vector3(ptr.getRefData().getPosition().pos),
esmStore.get<ESM::GameSetting>().find("fAlarmRadius")->getInt(), neighbors);
// Find an actor who witnessed the crime
for (std::vector<MWWorld::Ptr>::iterator it = neighbors.begin(); it != neighbors.end(); ++it)
{
if (*it == ptr) continue; // not the player
// Was the crime seen?
if ( ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) ||
type == OT_Assault )
{ {
// NPCs will always curse you when they notice you steal their items, even if they don't report the crime
if (it->first == victim && type == OT_Theft)
{
MWBase::Environment::get().getDialogueManager()->say(victim, "Thief");
}
// Actor has witnessed a crime. Will he report it? // Will the witness report the crime?
// (not sure, is > 0 correct?) if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm)
if (it->first.getClass().getCreatureStats(it->first).getAiSetting(CreatureStats::AI_Alarm).getModified() > 0)
{ {
// TODO: stats.setAlarmed(true) on NPCs within earshot reported = true;
// fAlarmRadius ? int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId();
reported=true;
break; // Tell everyone, including yourself
for (std::vector<MWWorld::Ptr>::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1)
{
if (*it1 == ptr) continue; // not the player
// TODO: Add more messages
if (type == OT_Theft)
MWBase::Environment::get().getDialogueManager()->say(*it1, "thief");
else if (type == OT_Assault)
MWBase::Environment::get().getDialogueManager()->say(*it1, "attack");
// Will other witnesses paticipate in crime
if ( it1->getClass().getCreatureStats(*it1).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm
|| type == OT_Assault )
{
it1->getClass().getNpcStats(*it1).setCrimeId(id);
}
}
break; // Someone saw the crime and everyone has been told
} }
} }
} }
if (reported) if (reported)
reportCrime(ptr, victim, type, arg); reportCrime(ptr, victim, type, arg);
return reported; return reported;
@ -838,6 +879,7 @@ namespace MWMechanics
void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg)
{ {
const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
// Bounty for each type of crime // Bounty for each type of crime
if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
arg = store.find("iCrimeTresspass")->getInt(); arg = store.find("iCrimeTresspass")->getInt();
@ -850,32 +892,10 @@ namespace MWMechanics
else if (type == OT_Theft) else if (type == OT_Theft)
arg *= store.find("fCrimeStealing")->getFloat(); arg *= store.find("fCrimeStealing")->getFloat();
// TODO: In some cases (type == Assault), if no NPCs are within earshot, the report will have no effect.
// however other crime types seem to be always produce a bounty.
MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}");
ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty() ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty()
+ arg); + arg);
if (!victim.isEmpty())
{
int fight = 0;
// Increase in fight rating for each type of crime
if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
fight = store.find("iFightTrespass")->getFloat();
else if (type == OT_Pickpocket)
fight = store.find("iFightPickpocket")->getInt();
else if (type == OT_Assault)
fight = store.find("iFightAttack")->getInt();
else if (type == OT_Murder)
fight = store.find("iFightKilling")->getInt();
else if (type == OT_Theft)
fight = store.find("fFightStealing")->getFloat();
// Not sure if this should be permanent?
fight = victim.getClass().getCreatureStats(victim).getAiSetting(CreatureStats::AI_Fight).getBase() + fight;
victim.getClass().getCreatureStats(victim).setAiSetting(CreatureStats::AI_Fight, fight);
}
// If committing a crime against a faction member, expell from the faction // If committing a crime against a faction member, expell from the faction
if (!victim.isEmpty() && victim.getClass().isNpc()) if (!victim.isEmpty() && victim.getClass().isNpc())
{ {
@ -887,8 +907,6 @@ namespace MWMechanics
ptr.getClass().getNpcStats(ptr).expell(factionID); ptr.getClass().getNpcStats(ptr).expell(factionID);
} }
} }
// TODO: make any guards in the area try to arrest the player
} }
bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer)

@ -29,6 +29,7 @@ MWMechanics::NpcStats::NpcStats()
, mLevelProgress(0) , mLevelProgress(0)
, mDisposition(0) , mDisposition(0)
, mReputation(0) , mReputation(0)
, mCrimeId(-1)
, mWerewolfKills (0) , mWerewolfKills (0)
, mProfit(0) , mProfit(0)
, mTimeToStartDrowning(20.0) , mTimeToStartDrowning(20.0)
@ -340,6 +341,16 @@ void MWMechanics::NpcStats::setReputation(int reputation)
mReputation = reputation; mReputation = reputation;
} }
int MWMechanics::NpcStats::getCrimeId() const
{
return mCrimeId;
}
void MWMechanics::NpcStats::setCrimeId(int id)
{
mCrimeId = id;
}
bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int rank) const bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int rank) const
{ {
if (rank<0 || rank>=10) if (rank<0 || rank>=10)
@ -441,6 +452,8 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const
mWerewolfSkill[i].writeState (state.mSkills[i].mWerewolf); mWerewolfSkill[i].writeState (state.mSkills[i].mWerewolf);
} }
state.mCrimeId = mCrimeId;
state.mBounty = mBounty; state.mBounty = mBounty;
for (std::set<std::string>::const_iterator iter (mExpelled.begin()); for (std::set<std::string>::const_iterator iter (mExpelled.begin());
@ -493,6 +506,7 @@ void MWMechanics::NpcStats::readState (const ESM::NpcStats& state)
mWerewolfSkill[i].readState (state.mSkills[i].mWerewolf); mWerewolfSkill[i].readState (state.mSkills[i].mWerewolf);
} }
mCrimeId = state.mCrimeId;
mBounty = state.mBounty; mBounty = state.mBounty;
mReputation = state.mReputation; mReputation = state.mReputation;
mWerewolfKills = state.mWerewolfKills; mWerewolfKills = state.mWerewolfKills;

@ -37,6 +37,7 @@ namespace MWMechanics
std::set<std::string> mExpelled; std::set<std::string> mExpelled;
std::map<std::string, int> mFactionReputation; std::map<std::string, int> mFactionReputation;
int mReputation; int mReputation;
int mCrimeId;
int mWerewolfKills; int mWerewolfKills;
int mProfit; int mProfit;
float mAttackStrength; float mAttackStrength;
@ -63,13 +64,14 @@ namespace MWMechanics
void modifyProfit(int diff); void modifyProfit(int diff);
int getBaseDisposition() const; int getBaseDisposition() const;
void setBaseDisposition(int disposition); void setBaseDisposition(int disposition);
int getReputation() const; int getReputation() const;
void setReputation(int reputation); void setReputation(int reputation);
int getCrimeId() const;
void setCrimeId(int id);
const SkillValue& getSkill (int index) const; const SkillValue& getSkill (int index) const;
SkillValue& getSkill (int index); SkillValue& getSkill (int index);

@ -1,7 +1,5 @@
#include "pathfinding.hpp" #include "pathfinding.hpp"
#include <map>
#include "OgreMath.h" #include "OgreMath.h"
#include "OgreVector3.h" #include "OgreVector3.h"
@ -37,19 +35,37 @@ namespace
return sqrt(x * x + y * y + z * z); return sqrt(x * x + y * y + z * z);
} }
int getClosestPoint(const ESM::Pathgrid* grid, float x, float y, float z) // Slightly cheaper version for comparisons.
// Caller needs to be careful for very short distances (i.e. less than 1)
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
//
float distanceSquared(ESM::Pathgrid::Point point, Ogre::Vector3 pos)
{
return Ogre::Vector3(point.mX, point.mY, point.mZ).squaredDistance(pos);
}
// Return the closest pathgrid point index from the specified position co
// -ordinates. NOTE: Does not check if there is a sensible way to get there
// (e.g. a cliff in front).
//
// NOTE: pos is expected to be in local co-ordinates, as is grid->mPoints
//
int getClosestPoint(const ESM::Pathgrid* grid, Ogre::Vector3 pos)
{ {
if(!grid || grid->mPoints.empty()) if(!grid || grid->mPoints.empty())
return -1; return -1;
float distanceBetween = distance(grid->mPoints[0], x, y, z); float distanceBetween = distanceSquared(grid->mPoints[0], pos);
int closestIndex = 0; int closestIndex = 0;
// TODO: if this full scan causes performance problems mapping pathgrid
// points to a quadtree may help
for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++)
{ {
if(distance(grid->mPoints[counter], x, y, z) < distanceBetween) float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos);
if(potentialDistBetween < distanceBetween)
{ {
distanceBetween = distance(grid->mPoints[counter], x, y, z); distanceBetween = potentialDistBetween;
closestIndex = counter; closestIndex = counter;
} }
} }
@ -57,96 +73,39 @@ namespace
return closestIndex; return closestIndex;
} }
/*std::list<ESM::Pathgrid::Point> reconstructPath(const std::vector<MWMechanics::PathFinder::Node>& graph,const ESM::Pathgrid* pathgrid, int lastNode,float xCell, float yCell) // Chooses a reachable end pathgrid point. start is assumed reachable.
{ std::pair<int, bool> getClosestReachablePoint(const ESM::Pathgrid* grid,
std::list<ESM::Pathgrid::Point> path; const MWWorld::CellStore *cell,
while(graph[lastNode].parent != -1) Ogre::Vector3 pos, int start)
{
//std::cout << "not empty" << xCell;
ESM::Pathgrid::Point pt = pathgrid->mPoints[lastNode];
pt.mX += xCell;
pt.mY += yCell;
path.push_front(pt);
lastNode = graph[lastNode].parent;
}
return path;
}*/
/*std::list<ESM::Pathgrid::Point> buildPath2(const ESM::Pathgrid* pathgrid,int start,int goal,float xCell = 0, float yCell = 0)
{ {
std::vector<Node> graph; if(!grid || grid->mPoints.empty())
for(unsigned int i = 0; i < pathgrid->mPoints.size(); i++) return std::pair<int, bool> (-1, false);
{
Node node;
node.label = i;
node.parent = -1;
graph.push_back(node);
}
for(unsigned int i = 0; i < pathgrid->mEdges.size(); i++)
{
Edge edge;
edge.destination = pathgrid->mEdges[i].mV1;
edge.cost = distance(pathgrid->mPoints[pathgrid->mEdges[i].mV0],pathgrid->mPoints[pathgrid->mEdges[i].mV1]);
graph[pathgrid->mEdges[i].mV0].edges.push_back(edge);
edge.destination = pathgrid->mEdges[i].mV0;
graph[pathgrid->mEdges[i].mV1].edges.push_back(edge);
}
std::vector<float> g_score(pathgrid->mPoints.size(),-1.);
std::vector<float> f_score(pathgrid->mPoints.size(),-1.);
g_score[start] = 0;
f_score[start] = distance(pathgrid->mPoints[start],pathgrid->mPoints[goal]);
std::list<int> openset;
std::list<int> closedset;
openset.push_back(start);
int current = -1;
while(!openset.empty()) float distanceBetween = distanceSquared(grid->mPoints[0], pos);
int closestIndex = 0;
int closestReachableIndex = 0;
// TODO: if this full scan causes performance problems mapping pathgrid
// points to a quadtree may help
for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++)
{ {
current = openset.front(); float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos);
openset.pop_front(); if(potentialDistBetween < distanceBetween)
if(current == goal) break;
closedset.push_back(current);
for(int j = 0;j<graph[current].edges.size();j++)
{ {
//int next = graph[current].edges[j].destination // found a closer one
if(std::find(closedset.begin(),closedset.end(),graph[current].edges[j].destination) == closedset.end()) distanceBetween = potentialDistBetween;
closestIndex = counter;
if (cell->isPointConnected(start, counter))
{ {
int dest = graph[current].edges[j].destination; closestReachableIndex = counter;
float tentative_g = g_score[current] + graph[current].edges[j].cost;
bool isInOpenSet = std::find(openset.begin(),openset.end(),dest) != openset.end();
if(!isInOpenSet
|| tentative_g < g_score[dest] )
{
graph[dest].parent = current;
g_score[dest] = tentative_g;
f_score[dest] = tentative_g + distance(pathgrid->mPoints[dest],pathgrid->mPoints[goal]);
if(!isInOpenSet)
{
std::list<int>::iterator it = openset.begin();
for(it = openset.begin();it!= openset.end();it++)
{
if(g_score[*it]>g_score[dest])
break;
}
openset.insert(it,dest);
}
}
} }
} }
} }
return reconstructPath(graph,pathgrid,current,xCell,yCell); if(start == closestReachableIndex)
closestReachableIndex = -1; // couldn't find anyting other than start
}*/ return std::pair<int, bool>
(closestReachableIndex, closestReachableIndex == closestIndex);
}
} }
@ -154,7 +113,7 @@ namespace MWMechanics
{ {
PathFinder::PathFinder() PathFinder::PathFinder()
: mIsPathConstructed(false), : mIsPathConstructed(false),
mIsGraphConstructed(false), mPathgrid(NULL),
mCell(NULL) mCell(NULL)
{ {
} }
@ -166,173 +125,138 @@ namespace MWMechanics
mIsPathConstructed = false; mIsPathConstructed = false;
} }
void PathFinder::buildPathgridGraph(const ESM::Pathgrid* pathGrid) /*
{ * NOTE: This method may fail to find a path. The caller must check the
mGraph.clear(); * result before using it. If there is no path the AI routies need to
mGScore.resize(pathGrid->mPoints.size(),-1); * implement some other heuristics to reach the target.
mFScore.resize(pathGrid->mPoints.size(),-1); *
Node defaultNode; * NOTE: It may be desirable to simply go directly to the endPoint if for
defaultNode.label = -1; * example there are no pathgrids in this cell.
defaultNode.parent = -1; *
mGraph.resize(pathGrid->mPoints.size(),defaultNode); * NOTE: startPoint & endPoint are in world co-ordinates
for(unsigned int i = 0; i < pathGrid->mPoints.size(); i++) *
{ * Updates mPath using aStarSearch() or ray test (if shortcut allowed).
Node node; * mPath consists of pathgrid points, except the last element which is
node.label = i; * endPoint. This may be useful where the endPoint is not on a pathgrid
node.parent = -1; * point (e.g. combat). However, if the caller has already chosen a
mGraph[i] = node; * pathgrid point (e.g. wander) then it may be worth while to call
} * pop_back() to remove the redundant entry.
for(unsigned int i = 0; i < pathGrid->mEdges.size(); i++) *
{ * mPathConstructed is set true if successful, false if not
Edge edge; *
edge.destination = pathGrid->mEdges[i].mV1; * NOTE: co-ordinates must be converted prior to calling getClosestPoint()
edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0],pathGrid->mPoints[pathGrid->mEdges[i].mV1]); *
mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge); * |
edge.destination = pathGrid->mEdges[i].mV0; * | cell
mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); * | +-----------+
} * | | |
mIsGraphConstructed = true; * | | |
} * | | @ |
* | i | j |
void PathFinder::cleanUpAStar() * |<--->|<---->| |
{ * | +-----------+
for(int i=0;i<static_cast<int> (mGraph.size());i++) * | k
{ * |<---------->| world
mGraph[i].parent = -1; * +-----------------------------
mGScore[i] = -1; *
mFScore[i] = -1; * i = x value of cell itself (multiply by ESM::Land::REAL_SIZE to convert)
} * j = @.x in local co-ordinates (i.e. within the cell)
} * k = @.x in world co-ordinates
*/
std::list<ESM::Pathgrid::Point> PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell, float yCell) void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint,
const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell,
bool allowShortcuts)
{ {
cleanUpAStar(); mPath.clear();
mGScore[start] = 0;
mFScore[start] = distance(pathGrid->mPoints[start],pathGrid->mPoints[goal]);
std::list<int> openset;
std::list<int> closedset;
openset.push_back(start);
int current = -1;
while(!openset.empty()) if(allowShortcuts)
{ {
current = openset.front(); // if there's a ray cast hit, can't take a direct path
openset.pop_front(); if(!MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ,
endPoint.mX, endPoint.mY, endPoint.mZ))
if(current == goal) break;
closedset.push_back(current);
for(int j = 0;j<static_cast<int> (mGraph[current].edges.size());j++)
{ {
//int next = mGraph[current].edges[j].destination mPath.push_back(endPoint);
if(std::find(closedset.begin(),closedset.end(),mGraph[current].edges[j].destination) == closedset.end()) mIsPathConstructed = true;
{ return;
int dest = mGraph[current].edges[j].destination;
float tentative_g = mGScore[current] + mGraph[current].edges[j].cost;
bool isInOpenSet = std::find(openset.begin(),openset.end(),dest) != openset.end();
if(!isInOpenSet
|| tentative_g < mGScore[dest] )
{
mGraph[dest].parent = current;
mGScore[dest] = tentative_g;
mFScore[dest] = tentative_g + distance(pathGrid->mPoints[dest],pathGrid->mPoints[goal]);
if(!isInOpenSet)
{
std::list<int>::iterator it = openset.begin();
for(it = openset.begin();it!= openset.end();it++)
{
if(mGScore[*it]>mGScore[dest])
break;
}
openset.insert(it,dest);
}
}
}
} }
} }
std::list<ESM::Pathgrid::Point> path; if(mCell != cell || !mPathgrid)
while(mGraph[current].parent != -1)
{ {
//std::cout << "not empty" << xCell; mCell = cell;
ESM::Pathgrid::Point pt = pathGrid->mPoints[current]; mPathgrid = MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*mCell->getCell());
pt.mX += xCell;
pt.mY += yCell;
path.push_front(pt);
current = mGraph[current].parent;
} }
if(path.empty()) // Refer to AiWander reseach topic on openmw forums for some background.
// Maybe there is no pathgrid for this cell. Just go to destination and let
// physics take care of any blockages.
if(!mPathgrid || mPathgrid->mPoints.empty())
{ {
ESM::Pathgrid::Point pt = pathGrid->mPoints[goal]; mPath.push_back(endPoint);
pt.mX += xCell; mIsPathConstructed = true;
pt.mY += yCell; return;
path.push_front(pt);
} }
return path; // NOTE: getClosestPoint expects local co-ordinates
} float xCell = 0;
float yCell = 0;
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, if (mCell->isExterior())
const MWWorld::CellStore* cell, bool allowShortcuts)
{
mPath.clear();
if(mCell != cell) mIsGraphConstructed = false;
mCell = cell;
if(allowShortcuts)
{ {
if(MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ, xCell = mCell->getCell()->mData.mX * ESM::Land::REAL_SIZE;
endPoint.mX, endPoint.mY, endPoint.mZ)) yCell = mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE;
allowShortcuts = false;
} }
if(!allowShortcuts) // NOTE: It is possible that getClosestPoint returns a pathgrind point index
// that is unreachable in some situations. e.g. actor is standing
// outside an area enclosed by walls, but there is a pathgrid
// point right behind the wall that is closer than any pathgrid
// point outside the wall
int startNode = getClosestPoint(mPathgrid,
Ogre::Vector3(startPoint.mX - xCell, startPoint.mY - yCell, startPoint.mZ));
// Some cells don't have any pathgrids at all
if(startNode != -1)
{ {
const ESM::Pathgrid *pathGrid = std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, cell,
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*mCell->getCell()); Ogre::Vector3(endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ),
float xCell = 0; startNode);
float yCell = 0;
if (mCell->isExterior()) // this shouldn't really happen, but just in case
if(endNode.first != -1)
{ {
xCell = mCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; mPath = mCell->aStarSearch(startNode, endNode.first);
yCell = mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE;
}
int startNode = getClosestPoint(pathGrid, startPoint.mX - xCell, startPoint.mY - yCell,startPoint.mZ);
int endNode = getClosestPoint(pathGrid, endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ);
if(startNode != -1 && endNode != -1)
{
if(!mIsGraphConstructed) buildPathgridGraph(pathGrid);
mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell);//findPath(startNode, endNode, mGraph);
if(!mPath.empty()) if(!mPath.empty())
{ {
mPath.push_back(endPoint);
mIsPathConstructed = true; mIsPathConstructed = true;
// Add the destination (which may be different to the closest
// pathgrid point). However only add if endNode was the closest
// point to endPoint.
//
// This logic can fail in the opposite situate, e.g. endPoint may
// have been reachable but happened to be very close to an
// unreachable pathgrid point.
//
// The AI routines will have to deal with such situations.
if(endNode.second)
mPath.push_back(endPoint);
} }
else
mIsPathConstructed = false;
} }
else
mIsPathConstructed = false;
} }
else else
{
mPath.push_back(endPoint);
mIsPathConstructed = true;
}
if(mPath.empty())
mIsPathConstructed = false; mIsPathConstructed = false;
return;
} }
float PathFinder::getZAngleToNext(float x, float y) const float PathFinder::getZAngleToNext(float x, float y) const
{ {
// This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call // This should never happen (programmers should have an if statement checking
// if otherwise). // mIsPathConstructed that prevents this call if otherwise).
if(mPath.empty()) if(mPath.empty())
return 0.; return 0.;
@ -344,6 +268,7 @@ namespace MWMechanics
return Ogre::Radian(Ogre::Math::ACos(directionY / directionResult) * sgn(Ogre::Math::ASin(directionX / directionResult))).valueDegrees(); return Ogre::Radian(Ogre::Math::ACos(directionY / directionResult) * sgn(Ogre::Math::ASin(directionX / directionResult))).valueDegrees();
} }
// Used by AiCombat, use Euclidean distance
float PathFinder::getDistToNext(float x, float y, float z) float PathFinder::getDistToNext(float x, float y, float z)
{ {
ESM::Pathgrid::Point nextPoint = *mPath.begin(); ESM::Pathgrid::Point nextPoint = *mPath.begin();
@ -384,6 +309,7 @@ namespace MWMechanics
return false; return false;
} }
// used by AiCombat, see header for the rationale
void PathFinder::syncStart(const std::list<ESM::Pathgrid::Point> &path) void PathFinder::syncStart(const std::list<ESM::Pathgrid::Point> &path)
{ {
if (mPath.size() < 2) if (mPath.size() < 2)
@ -403,4 +329,3 @@ namespace MWMechanics
} }
} }

@ -34,8 +34,6 @@ namespace MWMechanics
void clearPath(); void clearPath();
void buildPathgridGraph(const ESM::Pathgrid* pathGrid);
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, bool allowShortcuts = true); const MWWorld::CellStore* cell, bool allowShortcuts = true);
@ -64,9 +62,10 @@ namespace MWMechanics
return mPath; return mPath;
} }
//When first point of newly created path is the nearest to actor point, then // When first point of newly created path is the nearest to actor point,
//the cituation can occure when this point is undesirable (if the 2nd point of new path == the 1st point of old path) // then a situation can occure when this point is undesirable
//This functions deletes that point. // (if the 2nd point of new path == the 1st point of old path)
// This functions deletes that point.
void syncStart(const std::list<ESM::Pathgrid::Point> &path); void syncStart(const std::list<ESM::Pathgrid::Point> &path);
void addPointToPath(ESM::Pathgrid::Point &point) void addPointToPath(ESM::Pathgrid::Point &point)
@ -76,30 +75,11 @@ namespace MWMechanics
private: private:
struct Edge
{
int destination;
float cost;
};
struct Node
{
int label;
std::vector<Edge> edges;
int parent;//used in pathfinding
};
std::vector<float> mGScore;
std::vector<float> mFScore;
std::list<ESM::Pathgrid::Point> aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell = 0, float yCell = 0);
void cleanUpAStar();
std::vector<Node> mGraph;
bool mIsPathConstructed; bool mIsPathConstructed;
std::list<ESM::Pathgrid::Point> mPath; std::list<ESM::Pathgrid::Point> mPath;
bool mIsGraphConstructed;
const ESM::Pathgrid *mPathgrid;
const MWWorld::CellStore* mCell; const MWWorld::CellStore* mCell;
}; };
} }

@ -0,0 +1,334 @@
#include "pathgrid.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/cellstore.hpp"
namespace
{
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
//
// One of the smallest cost in Seyda Neen is between points 77 & 78:
// pt x y
// 77 = 8026, 4480
// 78 = 7986, 4218
//
// Euclidean distance is about 262 (ignoring z) and Manhattan distance is 300
// (again ignoring z). Using a value of about 300 for D seems like a reasonable
// starting point for experiments. If in doubt, just use value 1.
//
// The distance between 3 & 4 are pretty small, too.
// 3 = 5435, 223
// 4 = 5948, 193
//
// Approx. 514 Euclidean distance and 533 Manhattan distance.
//
float manhattan(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b)
{
return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ));
}
// Choose a heuristics - Note that these may not be the best for directed
// graphs with non-uniform edge costs.
//
// distance:
// - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2)
// - slower but more accurate
//
// Manhattan:
// - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z|
// - faster but not the shortest path
float costAStar(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b)
{
//return distance(a, b);
return manhattan(a, b);
}
}
namespace MWMechanics
{
PathgridGraph::PathgridGraph()
: mCell(NULL)
, mIsGraphConstructed(false)
, mPathgrid(NULL)
, mGraph(0)
, mSCCId(0)
, mSCCIndex(0)
, mIsExterior(0)
{
}
/*
* mGraph is populated with the cost of each allowed edge.
*
* The data structure is based on the code in buildPath2() but modified.
* Please check git history if interested.
*
* mGraph[v].edges[i].index = w
*
* v = point index of location "from"
* i = index of edges from point v
* w = point index of location "to"
*
*
* Example: (notice from p(0) to p(2) is not allowed in this example)
*
* mGraph[0].edges[0].index = 1
* .edges[1].index = 3
*
* mGraph[1].edges[0].index = 0
* .edges[1].index = 2
* .edges[2].index = 3
*
* mGraph[2].edges[0].index = 1
*
* (etc, etc)
*
*
* low
* cost
* p(0) <---> p(1) <------------> p(2)
* ^ ^
* | |
* | +-----> p(3)
* +---------------->
* high cost
*/
bool PathgridGraph::load(const ESM::Cell* cell)
{
if(!cell)
return false;
mCell = cell;
mIsExterior = cell->isExterior();
mPathgrid = MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*cell);
if(!mPathgrid)
return false;
if(mIsGraphConstructed)
return true;
mGraph.resize(mPathgrid->mPoints.size());
for(int i = 0; i < static_cast<int> (mPathgrid->mEdges.size()); i++)
{
ConnectedPoint neighbour;
neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0],
mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]);
// forward path of the edge
neighbour.index = mPathgrid->mEdges[i].mV1;
mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour);
// reverse path of the edge
// NOTE: These are redundant, ESM already contains the required reverse paths
//neighbour.index = mPathgrid->mEdges[i].mV0;
//mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour);
}
buildConnectedPoints();
mIsGraphConstructed = true;
return true;
}
// v is the pathgrid point index (some call them vertices)
void PathgridGraph::recursiveStrongConnect(int v)
{
mSCCPoint[v].first = mSCCIndex; // index
mSCCPoint[v].second = mSCCIndex; // lowlink
mSCCIndex++;
mSCCStack.push_back(v);
int w;
for(int i = 0; i < static_cast<int> (mGraph[v].edges.size()); i++)
{
w = mGraph[v].edges[i].index;
if(mSCCPoint[w].first == -1) // not visited
{
recursiveStrongConnect(w); // recurse
mSCCPoint[v].second = std::min(mSCCPoint[v].second,
mSCCPoint[w].second);
}
else
{
if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end())
mSCCPoint[v].second = std::min(mSCCPoint[v].second,
mSCCPoint[w].first);
}
}
if(mSCCPoint[v].second == mSCCPoint[v].first)
{ // new component
do
{
w = mSCCStack.back();
mSCCStack.pop_back();
mGraph[w].componentId = mSCCId;
}
while(w != v);
mSCCId++;
}
return;
}
/*
* mGraph contains the strongly connected component group id's along
* with pre-calculated edge costs.
*
* A cell can have disjointed pathgrids, e.g. Seyda Neen has 3
*
* mGraph for Seyda Neen will therefore have 3 different values. When
* selecting a random pathgrid point for AiWander, mGraph can be checked
* for quickly finding whether the destination is reachable.
*
* Otherwise, buildPath can automatically select a closest reachable end
* pathgrid point (reachable from the closest start point).
*
* Using Tarjan's algorithm:
*
* mGraph | graph G |
* mSCCPoint | V | derived from mPoints
* mGraph[v].edges | E (for v) |
* mSCCIndex | index | tracking smallest unused index
* mSCCStack | S |
* mGraph[v].edges[i].index | w |
*
*/
void PathgridGraph::buildConnectedPoints()
{
// both of these are set to zero in the constructor
//mSCCId = 0; // how many strongly connected components in this cell
//mSCCIndex = 0;
int pointsSize = mPathgrid->mPoints.size();
mSCCPoint.resize(pointsSize, std::pair<int, int> (-1, -1));
mSCCStack.reserve(pointsSize);
for(int v = 0; v < static_cast<int> (pointsSize); v++)
{
if(mSCCPoint[v].first == -1) // undefined (haven't visited)
recursiveStrongConnect(v);
}
}
bool PathgridGraph::isPointConnected(const int start, const int end) const
{
return (mGraph[start].componentId == mGraph[end].componentId);
}
/*
* NOTE: Based on buildPath2(), please check git history if interested
* Should consider using a 3rd party library version (e.g. boost)
*
* Find the shortest path to the target goal using a well known algorithm.
* Uses mGraph which has pre-computed costs for allowed edges. It is assumed
* that mGraph is already constructed.
*
* Should be possible to make this MT safe.
*
* Returns path which may be empty. path contains pathgrid points in local
* cell co-ordinates (indoors) or world co-ordinates (external).
*
* Input params:
* start, goal - pathgrid point indexes (for this cell)
*
* Variables:
* openset - point indexes to be traversed, lowest cost at the front
* closedset - point indexes already traversed
* gScore - past accumulated costs vector indexed by point index
* fScore - future estimated costs vector indexed by point index
*
* TODO: An intersting exercise might be to cache the paths created for a
* start/goal pair. To cache the results the paths need to be in
* pathgrid points form (currently they are converted to world
* co-ordinates). Essentially trading speed w/ memory.
*/
std::list<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start,
const int goal) const
{
std::list<ESM::Pathgrid::Point> path;
if(!isPointConnected(start, goal))
{
return path; // there is no path, return an empty path
}
int graphSize = mGraph.size();
std::vector<float> gScore (graphSize, -1);
std::vector<float> fScore (graphSize, -1);
std::vector<int> graphParent (graphSize, -1);
// gScore & fScore keep costs for each pathgrid point in mPoints
gScore[start] = 0;
fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]);
std::list<int> openset;
std::list<int> closedset;
openset.push_back(start);
int current = -1;
while(!openset.empty())
{
current = openset.front(); // front has the lowest cost
openset.pop_front();
if(current == goal)
break;
closedset.push_back(current); // remember we've been here
// check all edges for the current point index
for(int j = 0; j < static_cast<int> (mGraph[current].edges.size()); j++)
{
if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) ==
closedset.end())
{
// not in closedset - i.e. have not traversed this edge destination
int dest = mGraph[current].edges[j].index;
float tentative_g = gScore[current] + mGraph[current].edges[j].cost;
bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end();
if(!isInOpenSet
|| tentative_g < gScore[dest])
{
graphParent[dest] = current;
gScore[dest] = tentative_g;
fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest],
mPathgrid->mPoints[goal]);
if(!isInOpenSet)
{
// add this edge to openset, lowest cost goes to the front
// TODO: if this causes performance problems a hash table may help
std::list<int>::iterator it = openset.begin();
for(it = openset.begin(); it!= openset.end(); it++)
{
if(fScore[*it] > fScore[dest])
break;
}
openset.insert(it, dest);
}
}
} // if in closedset, i.e. traversed this edge already, try the next edge
}
}
if(current != goal)
return path; // for some reason couldn't build a path
// reconstruct path to return, using world co-ordinates
float xCell = 0;
float yCell = 0;
if (mIsExterior)
{
xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE;
yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE;
}
while(graphParent[current] != -1)
{
ESM::Pathgrid::Point pt = mPathgrid->mPoints[current];
pt.mX += xCell;
pt.mY += yCell;
path.push_front(pt);
current = graphParent[current];
}
return path;
}
}

@ -0,0 +1,77 @@
#ifndef GAME_MWMECHANICS_PATHGRID_H
#define GAME_MWMECHANICS_PATHGRID_H
#include <components/esm/loadpgrd.hpp>
#include <list>
namespace ESM
{
class Cell;
}
namespace MWWorld
{
class CellStore;
}
namespace MWMechanics
{
class PathgridGraph
{
public:
PathgridGraph();
bool load(const ESM::Cell *cell);
// returns true if end point is strongly connected (i.e. reachable
// from start point) both start and end are pathgrid point indexes
bool isPointConnected(const int start, const int end) const;
// the input parameters are pathgrid point indexes
// the output list is in local (internal cells) or world (external
// cells) co-ordinates
std::list<ESM::Pathgrid::Point> aStarSearch(const int start,
const int end) const;
private:
const ESM::Cell *mCell;
const ESM::Pathgrid *mPathgrid;
bool mIsExterior;
struct ConnectedPoint // edge
{
int index; // pathgrid point index of neighbour
float cost;
};
struct Node // point
{
int componentId;
std::vector<ConnectedPoint> edges; // neighbours
};
// componentId is an integer indicating the groups of connected
// pathgrid points (all connected points will have the same value)
//
// In Seyda Neen there are 3:
//
// 52, 53 and 54 are one set (enclosed yard)
// 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office)
// all other pathgrid points are the third set
//
std::vector<Node> mGraph;
bool mIsGraphConstructed;
// variables used to calculate connected components
int mSCCId;
int mSCCIndex;
std::vector<int> mSCCStack;
typedef std::pair<int, int> VPair; // first is index, second is lowlink
std::vector<VPair> mSCCPoint;
// methods used to calculate connected components
void recursiveStrongConnect(int v);
void buildConnectedPoints();
};
}
#endif

@ -390,8 +390,13 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni
TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog"); TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog");
if (!tex.isNull()) if (!tex.isNull())
{ {
std::map <std::string, std::vector<Ogre::uint32> >::iterator anIter;
// get its buffer // get its buffer
if (mBuffers.find(texName) == mBuffers.end()) return; anIter = mBuffers.find(texName);
if (anIter == mBuffers.end()) return;
std::vector<Ogre::uint32>& aBuffer = (*anIter).second;
int i=0; int i=0;
for (int texV = 0; texV<sFogOfWarResolution; ++texV) for (int texV = 0; texV<sFogOfWarResolution; ++texV)
{ {
@ -399,17 +404,17 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni
{ {
float sqrDist = Math::Sqr((texU + mx*(sFogOfWarResolution-1)) - u*(sFogOfWarResolution-1)) float sqrDist = Math::Sqr((texU + mx*(sFogOfWarResolution-1)) - u*(sFogOfWarResolution-1))
+ Math::Sqr((texV + my*(sFogOfWarResolution-1)) - v*(sFogOfWarResolution-1)); + Math::Sqr((texV + my*(sFogOfWarResolution-1)) - v*(sFogOfWarResolution-1));
uint32 clr = mBuffers[texName][i]; uint32 clr = aBuffer[i];
uint8 alpha = (clr >> 24); uint8 alpha = (clr >> 24);
alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) );
mBuffers[texName][i] = (uint32) (alpha << 24); aBuffer[i] = (uint32) (alpha << 24);
++i; ++i;
} }
} }
// copy to the texture // copy to the texture
memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &mBuffers[texName][0], sFogOfWarResolution*sFogOfWarResolution*4); memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &aBuffer[0], sFogOfWarResolution*sFogOfWarResolution*4);
tex->getBuffer()->unlock(); tex->getBuffer()->unlock();
} }
} }

@ -364,10 +364,9 @@ void RenderingManager::update (float duration, bool paused)
static const int i1stPersonSneakDelta = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>() static const int i1stPersonSneakDelta = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("i1stPersonSneakDelta")->getInt(); .find("i1stPersonSneakDelta")->getInt();
if(isSneaking && !(isSwimming || isInAir)) if(!paused && isSneaking && !(isSwimming || isInAir))
mCamera->setSneakOffset(i1stPersonSneakDelta); mCamera->setSneakOffset(i1stPersonSneakDelta);
mOcclusionQuery->update(duration); mOcclusionQuery->update(duration);
mRendering.update(duration); mRendering.update(duration);

@ -21,6 +21,7 @@
#include "../mwbase/scriptmanager.hpp" #include "../mwbase/scriptmanager.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
@ -802,6 +803,7 @@ namespace MWScript
{ {
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
world->goToJail(); world->goToJail();
MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId();
} }
}; };
@ -812,7 +814,7 @@ namespace MWScript
{ {
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
player.getClass().getNpcStats(player).setBounty(0); player.getClass().getNpcStats(player).setBounty(0);
MWBase::Environment::get().getWorld()->confiscateStolenItems(player); MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId();
} }
}; };
@ -823,6 +825,8 @@ namespace MWScript
{ {
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
player.getClass().getNpcStats(player).setBounty(0); player.getClass().getNpcStats(player).setBounty(0);
MWBase::Environment::get().getWorld()->confiscateStolenItems(player);
MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId();
} }
}; };

@ -363,6 +363,7 @@ namespace MWWorld
loadRefs (store, esm); loadRefs (store, esm);
mState = State_Loaded; mState = State_Loaded;
mPathgridGraph.load(mCell);
} }
} }
@ -680,4 +681,14 @@ namespace MWWorld
{ {
return !(left==right); return !(left==right);
} }
bool CellStore::isPointConnected(const int start, const int end) const
{
return mPathgridGraph.isPointConnected(start, end);
}
std::list<ESM::Pathgrid::Point> CellStore::aStarSearch(const int start, const int end) const
{
return mPathgridGraph.aStarSearch(start, end);
}
} }

@ -8,6 +8,8 @@
#include "esmstore.hpp" #include "esmstore.hpp"
#include "cellreflist.hpp" #include "cellreflist.hpp"
#include "../mwmechanics/pathgrid.hpp"
namespace ESM namespace ESM
{ {
struct CellState; struct CellState;
@ -141,6 +143,10 @@ namespace MWWorld
throw std::runtime_error ("Storage for this type not exist in cells"); throw std::runtime_error ("Storage for this type not exist in cells");
} }
bool isPointConnected(const int start, const int end) const;
std::list<ESM::Pathgrid::Point> aStarSearch(const int start, const int end) const;
private: private:
template<class Functor, class List> template<class Functor, class List>
@ -166,6 +172,8 @@ namespace MWWorld
///< Make case-adjustments to \a ref and insert it into the respective container. ///< Make case-adjustments to \a ref and insert it into the respective container.
/// ///
/// Invalid \a ref objects are silently dropped. /// Invalid \a ref objects are silently dropped.
MWMechanics::PathgridGraph mPathgridGraph;
}; };
template<> template<>

@ -396,4 +396,14 @@ namespace MWWorld
void Class::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {} void Class::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {}
void Class::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const {} void Class::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const {}
int Class::getBaseGold(const MWWorld::Ptr& ptr) const
{
throw std::runtime_error("class does not support base gold");
}
bool Class::isClass(const MWWorld::Ptr& ptr, const std::string &className) const
{
return false;
}
} }

@ -332,6 +332,10 @@ namespace MWWorld
///< If there is no class for this pointer, an exception is thrown. ///< If there is no class for this pointer, an exception is thrown.
static void registerClass (const std::string& key, boost::shared_ptr<Class> instance); static void registerClass (const std::string& key, boost::shared_ptr<Class> instance);
virtual int getBaseGold(const MWWorld::Ptr& ptr) const;
virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const;
}; };
} }

@ -308,11 +308,17 @@ namespace MWWorld
break; break;
} }
Ogre::Vector3 oldPosition = newPosition;
// We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over) // We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over)
// NOTE: May need to stop slaughterfish step out of the water. // NOTE: stepMove modifies newPosition if successful
// NOTE: stepMove may modify newPosition if(stepMove(colobj, newPosition, velocity, remainingTime, engine))
if((canWalk || isBipedal || isNpc) && stepMove(colobj, newPosition, velocity, remainingTime, engine)) {
isOnGround = !(newPosition.z < waterlevel || isFlying); // Only on the ground if there's gravity // don't let slaughterfish move out of water after stepMove
if(ptr.getClass().canSwim(ptr) && newPosition.z > (waterlevel - halfExtents.z * 0.5))
newPosition = oldPosition;
else // Only on the ground if there's gravity
isOnGround = !(newPosition.z < waterlevel || isFlying);
}
else else
{ {
// Can't move this way, try to find another spot along the plane // Can't move this way, try to find another spot along the plane

@ -28,9 +28,11 @@ namespace MWWorld
: mCellStore(0), : mCellStore(0),
mLastKnownExteriorPosition(0,0,0), mLastKnownExteriorPosition(0,0,0),
mAutoMove(false), mAutoMove(false),
mForwardBackward (0), mForwardBackward(0),
mTeleported(false), mTeleported(false),
mMarkedCell(NULL) mMarkedCell(NULL),
mCurrentCrimeId(-1),
mPayedCrimeId(-1)
{ {
mPlayer.mBase = player; mPlayer.mBase = player;
mPlayer.mRef.mRefID = "player"; mPlayer.mRef.mRefID = "player";
@ -194,6 +196,9 @@ namespace MWWorld
mPlayer.save (player.mObject); mPlayer.save (player.mObject);
player.mCellId = mCellStore->getCell()->getCellId(); player.mCellId = mCellStore->getCell()->getCellId();
player.mCurrentCrimeId = mCurrentCrimeId;
player.mPayedCrimeId = mPayedCrimeId;
player.mBirthsign = mSign; player.mBirthsign = mSign;
player.mLastKnownExteriorPosition[0] = mLastKnownExteriorPosition.x; player.mLastKnownExteriorPosition[0] = mLastKnownExteriorPosition.x;
@ -239,6 +244,9 @@ namespace MWWorld
!world.getStore().get<ESM::BirthSign>().search (player.mBirthsign)) !world.getStore().get<ESM::BirthSign>().search (player.mBirthsign))
throw std::runtime_error ("invalid player state record (birthsign)"); throw std::runtime_error ("invalid player state record (birthsign)");
mCurrentCrimeId = player.mCurrentCrimeId;
mPayedCrimeId = player.mPayedCrimeId;
mSign = player.mBirthsign; mSign = player.mBirthsign;
mLastKnownExteriorPosition.x = player.mLastKnownExteriorPosition[0]; mLastKnownExteriorPosition.x = player.mLastKnownExteriorPosition[0];
@ -274,4 +282,19 @@ namespace MWWorld
return false; return false;
} }
int Player::getNewCrimeId()
{
return ++mCurrentCrimeId;
}
void Player::recordCrimeId()
{
mPayedCrimeId = mCurrentCrimeId;
}
int Player::getCrimeId() const
{
return mPayedCrimeId;
}
} }

@ -41,6 +41,10 @@ namespace MWWorld
bool mAutoMove; bool mAutoMove;
int mForwardBackward; int mForwardBackward;
bool mTeleported; bool mTeleported;
int mCurrentCrimeId; // the id assigned witnesses
int mPayedCrimeId; // the last id payed off (0 bounty)
public: public:
Player(const ESM::NPC *player, const MWBase::World& world); Player(const ESM::NPC *player, const MWBase::World& world);
@ -63,15 +67,12 @@ namespace MWWorld
MWWorld::Ptr getPlayer(); MWWorld::Ptr getPlayer();
void setBirthSign(const std::string &sign); void setBirthSign(const std::string &sign);
const std::string &getBirthSign() const; const std::string &getBirthSign() const;
void setDrawState (MWMechanics::DrawState_ state); void setDrawState (MWMechanics::DrawState_ state);
bool getAutoMove() const;
MWMechanics::DrawState_ getDrawState(); /// \todo constness MWMechanics::DrawState_ getDrawState(); /// \todo constness
bool getAutoMove() const;
void setAutoMove (bool enable); void setAutoMove (bool enable);
void setLeftRight (int value); void setLeftRight (int value);
@ -94,6 +95,10 @@ namespace MWWorld
void write (ESM::ESMWriter& writer) const; void write (ESM::ESMWriter& writer) const;
bool readRecord (ESM::ESMReader& reader, int32_t type); bool readRecord (ESM::ESMReader& reader, int32_t type);
int getNewCrimeId(); // get new id for witnesses
void recordCrimeId(); // record the payed crime id when bounty is 0
int getCrimeId() const; // get the last payed crime id
}; };
} }
#endif #endif

@ -9,6 +9,7 @@
#include <OgreSceneNode.h> #include <OgreSceneNode.h>
#include <libs/openengine/bullet/trace.h>
#include <libs/openengine/bullet/physic.hpp> #include <libs/openengine/bullet/physic.hpp>
#include <components/bsa/bsa_archive.hpp> #include <components/bsa/bsa_archive.hpp>
@ -1711,11 +1712,43 @@ namespace MWWorld
return pos.z < cell->getWaterLevel(); return pos.z < cell->getWaterLevel();
} }
// physactor->getOnGround() is not a reliable indicator of whether the actor
// is on the ground (defaults to false, which means code blocks such as
// CharacterController::update() may falsely detect "falling").
//
// Also, collisions can move z position slightly off zero, giving a false
// indication. In order to reduce false detection of jumping, small distance
// below the actor is detected and ignored. A value of 1.5 is used here, but
// something larger may be more suitable. This change should resolve Bug#1271.
//
// TODO: There might be better places to update PhysicActor::mOnGround.
bool World::isOnGround(const MWWorld::Ptr &ptr) const bool World::isOnGround(const MWWorld::Ptr &ptr) const
{ {
RefData &refdata = ptr.getRefData(); RefData &refdata = ptr.getRefData();
const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle());
return physactor && physactor->getOnGround();
if(!physactor)
return false;
if(physactor->getOnGround())
return true;
else
{
Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
OEngine::Physic::ActorTracer tracer;
// a small distance above collision object is considered "on ground"
tracer.findGround(physactor->getCollisionBody(),
pos,
pos - Ogre::Vector3(0, 0, 1.5f), // trace a small amount down
mPhysEngine);
if(tracer.mFraction < 1.0f) // collision, must be close to something below
{
const_cast<OEngine::Physic::PhysicActor *> (physactor)->setOnGround(true);
return true;
}
else
return false;
}
} }
bool World::vanityRotateCamera(float * rot) bool World::vanityRotateCamera(float * rot)
@ -2071,6 +2104,12 @@ namespace MWWorld
{ {
contentLoader.load(col.getPath(*it), idx); contentLoader.load(col.getPath(*it), idx);
} }
else
{
std::stringstream msg;
msg << "Failed loading " << *it << ": the content file does not exist";
throw std::runtime_error(msg.str());
}
} }
} }
@ -2752,6 +2791,8 @@ namespace MWWorld
message += "\n" + skillMsg; message += "\n" + skillMsg;
} }
// TODO: Sleep the player
std::vector<std::string> buttons; std::vector<std::string> buttons;
buttons.push_back("#{sOk}"); buttons.push_back("#{sOk}");
MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); MWBase::Environment::get().getWindowManager()->messageBox(message, buttons);

@ -11,13 +11,13 @@ namespace ESM
void Activator::load(ESMReader &esm) void Activator::load(ESMReader &esm)
{ {
mModel = esm.getHNString("MODL"); mModel = esm.getHNString("MODL");
mName = esm.getHNString("FNAM"); mName = esm.getHNOString("FNAM");
mScript = esm.getHNOString("SCRI"); mScript = esm.getHNOString("SCRI");
} }
void Activator::save(ESMWriter &esm) const void Activator::save(ESMWriter &esm) const
{ {
esm.writeHNCString("MODL", mModel); esm.writeHNCString("MODL", mModel);
esm.writeHNCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("SCRI", mScript);
} }

@ -34,7 +34,7 @@ unsigned int Armor::sRecordId = REC_ARMO;
void Armor::load(ESMReader &esm) void Armor::load(ESMReader &esm)
{ {
mModel = esm.getHNString("MODL"); mModel = esm.getHNString("MODL");
mName = esm.getHNString("FNAM"); mName = esm.getHNOString("FNAM");
mScript = esm.getHNOString("SCRI"); mScript = esm.getHNOString("SCRI");
esm.getHNT(mData, "AODT", 24); esm.getHNT(mData, "AODT", 24);
mIcon = esm.getHNOString("ITEX"); mIcon = esm.getHNOString("ITEX");
@ -45,7 +45,7 @@ void Armor::load(ESMReader &esm)
void Armor::save(ESMWriter &esm) const void Armor::save(ESMWriter &esm) const
{ {
esm.writeHNCString("MODL", mModel); esm.writeHNCString("MODL", mModel);
esm.writeHNCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("SCRI", mScript);
esm.writeHNT("AODT", mData, 24); esm.writeHNT("AODT", mData, 24);
esm.writeHNOCString("ITEX", mIcon); esm.writeHNOCString("ITEX", mIcon);

@ -12,13 +12,13 @@ namespace ESM
void BodyPart::load(ESMReader &esm) void BodyPart::load(ESMReader &esm)
{ {
mModel = esm.getHNString("MODL"); mModel = esm.getHNString("MODL");
mRace = esm.getHNString("FNAM"); mRace = esm.getHNOString("FNAM");
esm.getHNT(mData, "BYDT", 4); esm.getHNT(mData, "BYDT", 4);
} }
void BodyPart::save(ESMWriter &esm) const void BodyPart::save(ESMWriter &esm) const
{ {
esm.writeHNCString("MODL", mModel); esm.writeHNCString("MODL", mModel);
esm.writeHNCString("FNAM", mRace); esm.writeHNOCString("FNAM", mRace);
esm.writeHNT("BYDT", mData, 4); esm.writeHNT("BYDT", mData, 4);
} }

@ -10,7 +10,7 @@ namespace ESM
void BirthSign::load(ESMReader &esm) void BirthSign::load(ESMReader &esm)
{ {
mName = esm.getHNString("FNAM"); mName = esm.getHNOString("FNAM");
mTexture = esm.getHNOString("TNAM"); mTexture = esm.getHNOString("TNAM");
mDescription = esm.getHNOString("DESC"); mDescription = esm.getHNOString("DESC");
@ -19,7 +19,7 @@ void BirthSign::load(ESMReader &esm)
void BirthSign::save(ESMWriter &esm) const void BirthSign::save(ESMWriter &esm) const
{ {
esm.writeHNCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNOCString("TNAM", mTexture); esm.writeHNOCString("TNAM", mTexture);
esm.writeHNOCString("DESC", mDescription); esm.writeHNOCString("DESC", mDescription);

@ -41,7 +41,7 @@ const char *Class::sGmstSpecializationIds[3] = {
void Class::load(ESMReader &esm) void Class::load(ESMReader &esm)
{ {
mName = esm.getHNString("FNAM"); mName = esm.getHNOString("FNAM");
esm.getHNT(mData, "CLDT", 60); esm.getHNT(mData, "CLDT", 60);
if (mData.mIsPlayable > 1) if (mData.mIsPlayable > 1)
@ -51,7 +51,7 @@ void Class::load(ESMReader &esm)
} }
void Class::save(ESMWriter &esm) const void Class::save(ESMWriter &esm) const
{ {
esm.writeHNCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNT("CLDT", mData, 60); esm.writeHNT("CLDT", mData, 60);
esm.writeHNOString("DESC", mDescription); esm.writeHNOString("DESC", mDescription);
} }

@ -28,7 +28,7 @@ namespace ESM
void Faction::load(ESMReader &esm) void Faction::load(ESMReader &esm)
{ {
mName = esm.getHNString("FNAM"); mName = esm.getHNOString("FNAM");
// Read rank names. These are optional. // Read rank names. These are optional.
int i = 0; int i = 0;
@ -52,7 +52,7 @@ void Faction::load(ESMReader &esm)
} }
void Faction::save(ESMWriter &esm) const void Faction::save(ESMWriter &esm) const
{ {
esm.writeHNCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {

@ -11,7 +11,7 @@ namespace ESM
void Ingredient::load(ESMReader &esm) void Ingredient::load(ESMReader &esm)
{ {
mModel = esm.getHNString("MODL"); mModel = esm.getHNString("MODL");
mName = esm.getHNString("FNAM"); mName = esm.getHNOString("FNAM");
esm.getHNT(mData, "IRDT", 56); esm.getHNT(mData, "IRDT", 56);
mScript = esm.getHNOString("SCRI"); mScript = esm.getHNOString("SCRI");
mIcon = esm.getHNOString("ITEX"); mIcon = esm.getHNOString("ITEX");
@ -42,7 +42,7 @@ void Ingredient::load(ESMReader &esm)
void Ingredient::save(ESMWriter &esm) const void Ingredient::save(ESMWriter &esm) const
{ {
esm.writeHNCString("MODL", mModel); esm.writeHNCString("MODL", mModel);
esm.writeHNCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNT("IRDT", mData, 56); esm.writeHNT("IRDT", mData, 56);
esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("SCRI", mScript);
esm.writeHNOCString("ITEX", mIcon); esm.writeHNOCString("ITEX", mIcon);

@ -11,7 +11,7 @@ namespace ESM
void Lockpick::load(ESMReader &esm) void Lockpick::load(ESMReader &esm)
{ {
mModel = esm.getHNString("MODL"); mModel = esm.getHNString("MODL");
mName = esm.getHNString("FNAM"); mName = esm.getHNOString("FNAM");
esm.getHNT(mData, "LKDT", 16); esm.getHNT(mData, "LKDT", 16);
@ -22,7 +22,7 @@ void Lockpick::load(ESMReader &esm)
void Lockpick::save(ESMWriter &esm) const void Lockpick::save(ESMWriter &esm) const
{ {
esm.writeHNCString("MODL", mModel); esm.writeHNCString("MODL", mModel);
esm.writeHNCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNT("LKDT", mData, 16); esm.writeHNT("LKDT", mData, 16);
esm.writeHNOString("SCRI", mScript); esm.writeHNOString("SCRI", mScript);

@ -11,7 +11,7 @@ namespace ESM
void Probe::load(ESMReader &esm) void Probe::load(ESMReader &esm)
{ {
mModel = esm.getHNString("MODL"); mModel = esm.getHNString("MODL");
mName = esm.getHNString("FNAM"); mName = esm.getHNOString("FNAM");
esm.getHNT(mData, "PBDT", 16); esm.getHNT(mData, "PBDT", 16);
@ -22,7 +22,7 @@ void Probe::load(ESMReader &esm)
void Probe::save(ESMWriter &esm) const void Probe::save(ESMWriter &esm) const
{ {
esm.writeHNCString("MODL", mModel); esm.writeHNCString("MODL", mModel);
esm.writeHNCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNT("PBDT", mData, 16); esm.writeHNT("PBDT", mData, 16);
esm.writeHNOString("SCRI", mScript); esm.writeHNOString("SCRI", mScript);

@ -20,14 +20,14 @@ namespace ESM
void Race::load(ESMReader &esm) void Race::load(ESMReader &esm)
{ {
mName = esm.getHNString("FNAM"); mName = esm.getHNOString("FNAM");
esm.getHNT(mData, "RADT", 140); esm.getHNT(mData, "RADT", 140);
mPowers.load(esm); mPowers.load(esm);
mDescription = esm.getHNOString("DESC"); mDescription = esm.getHNOString("DESC");
} }
void Race::save(ESMWriter &esm) const void Race::save(ESMWriter &esm) const
{ {
esm.writeHNCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNT("RADT", mData, 140); esm.writeHNT("RADT", mData, 140);
mPowers.save(esm); mPowers.save(esm);
esm.writeHNOString("DESC", mDescription); esm.writeHNOString("DESC", mDescription);

@ -10,7 +10,7 @@ namespace ESM
void Region::load(ESMReader &esm) void Region::load(ESMReader &esm)
{ {
mName = esm.getHNString("FNAM"); mName = esm.getHNOString("FNAM");
if (esm.getVer() == VER_12) if (esm.getVer() == VER_12)
esm.getHNExact(&mData, sizeof(mData) - 2, "WEAT"); esm.getHNExact(&mData, sizeof(mData) - 2, "WEAT");
@ -32,7 +32,7 @@ void Region::load(ESMReader &esm)
} }
void Region::save(ESMWriter &esm) const void Region::save(ESMWriter &esm) const
{ {
esm.writeHNCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
if (esm.getVersion() == VER_12) if (esm.getVersion() == VER_12)
esm.writeHNT("WEAT", mData, sizeof(mData) - 2); esm.writeHNT("WEAT", mData, sizeof(mData) - 2);

@ -11,7 +11,7 @@ namespace ESM
void Repair::load(ESMReader &esm) void Repair::load(ESMReader &esm)
{ {
mModel = esm.getHNString("MODL"); mModel = esm.getHNString("MODL");
mName = esm.getHNString("FNAM"); mName = esm.getHNOString("FNAM");
esm.getHNT(mData, "RIDT", 16); esm.getHNT(mData, "RIDT", 16);
@ -22,7 +22,7 @@ void Repair::load(ESMReader &esm)
void Repair::save(ESMWriter &esm) const void Repair::save(ESMWriter &esm) const
{ {
esm.writeHNCString("MODL", mModel); esm.writeHNCString("MODL", mModel);
esm.writeHNCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNT("RIDT", mData, 16); esm.writeHNT("RIDT", mData, 16);
esm.writeHNOString("SCRI", mScript); esm.writeHNOString("SCRI", mScript);

@ -10,7 +10,7 @@ namespace ESM
void Sound::load(ESMReader &esm) void Sound::load(ESMReader &esm)
{ {
mSound = esm.getHNString("FNAM"); mSound = esm.getHNOString("FNAM");
esm.getHNT(mData, "DATA", 3); esm.getHNT(mData, "DATA", 3);
/* /*
cout << "vol=" << (int)data.volume cout << "vol=" << (int)data.volume
@ -21,7 +21,7 @@ void Sound::load(ESMReader &esm)
} }
void Sound::save(ESMWriter &esm) const void Sound::save(ESMWriter &esm) const
{ {
esm.writeHNCString("FNAM", mSound); esm.writeHNOCString("FNAM", mSound);
esm.writeHNT("DATA", mData, 3); esm.writeHNT("DATA", mData, 3);
} }

@ -67,6 +67,9 @@ void ESM::NpcStats::load (ESMReader &esm)
mLevelHealthBonus = 0; mLevelHealthBonus = 0;
esm.getHNOT (mLevelHealthBonus, "LVLH"); esm.getHNOT (mLevelHealthBonus, "LVLH");
mCrimeId = -1;
esm.getHNOT (mCrimeId, "CRID");
} }
void ESM::NpcStats::save (ESMWriter &esm) const void ESM::NpcStats::save (ESMWriter &esm) const
@ -130,4 +133,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const
if (mLevelHealthBonus) if (mLevelHealthBonus)
esm.writeHNT ("LVLH", mLevelHealthBonus); esm.writeHNT ("LVLH", mLevelHealthBonus);
if (mCrimeId != -1)
esm.writeHNT ("CRID", mCrimeId);
} }

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

Loading…
Cancel
Save