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

loadfix
pvdk 11 years ago
commit a2c129f655

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

@ -24,7 +24,7 @@ opencs_units (model/world
opencs_units_noqt (model/world
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
@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc
opencs_units (view/world
table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator
cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool
scenetoolmode infocreator scriptedit dialoguesubview previewsubview
scenetoolmode infocreator scriptedit dialoguesubview previewsubview regionmap
)
opencs_units (view/render

@ -9,6 +9,9 @@
namespace CSMWorld
{
/// \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
{
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>
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
{

@ -1,6 +1,7 @@
#include "regionmap.hpp"
#include <cmath>
#include <algorithm>
#include <QBrush>
@ -25,17 +26,28 @@ CSMWorld::RegionMap::CellDescription::CellDescription (const Record<Cell>& cell)
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,
(mMax.second-mMin.second - index.row()-1)+mMin.second);
return mMin.move (index.column(), mMax.getY()-mMin.getY() - index.row()-1);
}
QModelIndex CSMWorld::RegionMap::getIndex (const CellIndex& index) const
QModelIndex CSMWorld::RegionMap::getIndex (const CellCoordinates& index) const
{
// I hate you, Qt API naming scheme!
return QAbstractTableModel::index (mMax.second-mMin.second - (index.second-mMin.second)-1,
index.first-mMin.first);
return QAbstractTableModel::index (mMax.getY()-mMin.getY() - (index.getY()-mMin.getY())-1,
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()
@ -70,21 +82,21 @@ void CSMWorld::RegionMap::buildMap()
{
CellDescription description (cell);
CellIndex index (cell2.mData.mX, cell2.mData.mY);
CellCoordinates index = getIndex (cell2);
mMap.insert (std::make_pair (index, description));
}
}
std::pair<CellIndex, CellIndex> mapSize = getSize();
std::pair<CellCoordinates, CellCoordinates> mapSize = getSize();
mMin = mapSize.first;
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())
{
@ -114,7 +126,7 @@ void CSMWorld::RegionMap::addCells (int start, int end)
if (cell2.isExterior())
{
CellIndex index (cell2.mData.mX, cell2.mData.mY);
CellCoordinates index = getIndex (cell2);
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())
{
@ -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::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)
{
if (!iter->second.mRegion.empty() &&
@ -176,90 +188,57 @@ void CSMWorld::RegionMap::updateRegions (const std::vector<std::string>& regions
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;
if (diff<0)
{
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();
}
beginInsertColumns (QModelIndex(), 0, std::abs (diff)-1);
mMin = CellCoordinates (size.first.getX(), mMin.getY());
endInsertColumns();
}
if (int diff = size.first.getY() - mMin.getY())
{
int diff = size.first.second - mMin.second;
if (diff<0)
{
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();
}
beginInsertRows (QModelIndex(), 0, std::abs (diff)-1);
mMin = CellCoordinates (mMin.getX(), size.first.getY());
endInsertRows();
}
if (int diff = size.second.getX() - mMax.getX())
{
int diff = size.second.first - mMax.first;
int columns = columnCount();
if (diff>0)
{
int columns = columnCount();
beginInsertColumns (QModelIndex(), columns, columns+diff-1);
mMax.first = size.second.first;
endInsertColumns();
}
else if (diff<0)
{
int columns = columnCount();
else
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)
{
int rows = rowCount();
beginInsertRows (QModelIndex(), rows, rows+diff-1);
mMax.second = size.second.second;
endInsertRows();
}
else if (diff<0)
{
int rows = rowCount();
else
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()
const
std::pair<CSMWorld::CellCoordinates, CSMWorld::CellCoordinates> CSMWorld::RegionMap::getSize() const
{
const IdCollection<Cell>& cells = mData.getCells();
int size = cells.getSize();
CellIndex min (0, 0);
CellIndex max (0, 0);
CellCoordinates min (0, 0);
CellCoordinates max (0, 0);
for (int i=0; i<size; ++i)
{
@ -269,24 +248,24 @@ std::pair<CSMWorld::RegionMap::CellIndex, CSMWorld::RegionMap::CellIndex> CSMWor
if (cell2.isExterior())
{
CellIndex index (cell2.mData.mX, cell2.mData.mY);
CellCoordinates index = getIndex (cell2);
if (min==max)
{
min = index;
max = std::make_pair (min.first+1, min.second+1);
max = min.move (1, 1);
}
else
{
if (index.first<min.first)
min.first = index.first;
else if (index.first>=max.first)
max.first = index.first + 1;
if (index.second<min.second)
min.second = index.second;
else if (index.second>=max.second)
max.second = index.second + 1;
if (index.getX()<min.getX())
min = CellCoordinates (index.getX(), min.getY());
else if (index.getX()>=max.getX())
max = CellCoordinates (index.getX()+1, max.getY());
if (index.getY()<min.getY())
min = CellCoordinates (min.getX(), index.getY());
else if (index.getY()>=max.getY())
max = CellCoordinates (max.getX(), index.getY() + 1);
}
}
}
@ -323,7 +302,7 @@ int CSMWorld::RegionMap::rowCount (const QModelIndex& parent) const
if (parent.isValid())
return 0;
return mMax.second-mMin.second;
return mMax.getY()-mMin.getY();
}
int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const
@ -331,7 +310,7 @@ int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const
if (parent.isValid())
return 0;
return mMax.first-mMin.first;
return mMax.getX()-mMin.getX();
}
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.
std::map<CellIndex, CellDescription>::const_iterator cell =
std::map<CellCoordinates, CellDescription>::const_iterator cell =
mMap.find (getIndex (index));
if (cell!=mMap.end())
@ -370,13 +349,13 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const
if (role==Qt::ToolTipRole)
{
CellIndex cellIndex = getIndex (index);
CellCoordinates cellIndex = getIndex (index);
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);
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());
}
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();
}
Qt::ItemFlags CSMWorld::RegionMap::flags (const QModelIndex& index) const
{
if (mMap.find (getIndex (index))!=mMap.end())
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
return 0;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
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();
if (cell2.isExterior())
{
CellIndex index (cell2.mData.mX, cell2.mData.mY);
removeCell (index);
}
removeCell (getIndex (cell2));
}
}

@ -9,6 +9,7 @@
#include "record.hpp"
#include "cell.hpp"
#include "cellcoordinates.hpp"
namespace CSMWorld
{
@ -23,7 +24,11 @@ namespace CSMWorld
public:
typedef std::pair<int, int> CellIndex;
enum Role
{
Role_Region = Qt::UserRole,
Role_CellId = Qt::UserRole+1
};
private:
@ -39,27 +44,29 @@ namespace CSMWorld
};
Data& mData;
std::map<CellIndex, CellDescription> mMap;
CellIndex mMin; ///< inclusive
CellIndex mMax; ///< exclusive
std::map<CellCoordinates, CellDescription> mMap;
CellCoordinates mMin; ///< inclusive
CellCoordinates mMax; ///< exclusive
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)
QModelIndex getIndex (const CellIndex& index) const;
QModelIndex getIndex (const CellCoordinates& index) const;
CellCoordinates getIndex (const Cell& cell) const;
void buildRegions();
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
// performed)
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)
void addRegion (const std::string& region, unsigned int colour);
@ -78,7 +85,7 @@ namespace CSMWorld
void updateSize();
std::pair<CellIndex, CellIndex> getSize() const;
std::pair<CellCoordinates, CellCoordinates> getSize() const;
public:
@ -89,6 +96,8 @@ namespace CSMWorld
virtual int columnCount (const QModelIndex& parent = QModelIndex()) 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;

@ -193,11 +193,4 @@ std::string CSVFilter::EditWidget::generateFilter (std::pair< std::string, std::
}
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);
void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
Qt::DropAction action);
signals:
void filterChanged (boost::shared_ptr<CSMFilter::Node> filter);
@ -47,10 +50,7 @@ namespace CSVFilter
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);
RecordFilterBox *recordFilterBox = new RecordFilterBox (data, this);
mRecordFilterBox = new RecordFilterBox (data, this);
layout->addWidget (recordFilterBox);
layout->addWidget (mRecordFilterBox);
setLayout (layout);
connect (recordFilterBox,
connect (mRecordFilterBox,
SIGNAL (filterChanged (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);
}
void CSVFilter::FilterBox::setRecordFilter (const std::string& filter)
{
mRecordFilterBox->setFilter (filter);
}
void CSVFilter::FilterBox::dropEvent (QDropEvent* event)
{
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();
}
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
{
class RecordFilterBox;
class FilterBox : public QWidget
{
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 dropEvent (QDropEvent* event);
void dragMoveEvent(QDragMoveEvent *event);
public:
FilterBox (CSMWorld::Data& data, QWidget *parent = 0);
signals:
void recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter);
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));
EditWidget *editWidget = new EditWidget (data, this);
mEdit = new EditWidget (data, this);
layout->addWidget (editWidget);
layout->addWidget (mEdit);
setLayout (layout);
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>)));
}
connect(this, SIGNAL(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)),
editWidget, SLOT(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)));
void CSVFilter::RecordFilterBox::setFilter (const std::string& filter)
{
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
{
class EditWidget;
class RecordFilterBox : public QWidget
{
Q_OBJECT
EditWidget *mEdit;
public:
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,
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 <sstream>
CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget *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
#define OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H
#include "../../model/world/cellselection.hpp"
#include "worldspacewidget.hpp"
namespace CSVRender
@ -9,9 +11,22 @@ namespace CSVRender
{
Q_OBJECT
CSMWorld::CellSelection mSelection;
public:
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);
}
void CSVRender::WorldspaceWidget::useViewHint (const std::string& hint) {}
void CSVRender::WorldspaceWidget::selectDefaultNavigationMode()
{
setNavigation (&m1st);

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

@ -28,7 +28,7 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo
else
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);
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 <QTableView>
#include <QHeaderView>
#include "regionmap.hpp"
CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId,
CSMDoc::Document& document)
: CSVDoc::SubView (universalId)
{
mTable = new QTableView (this);
mRegionMap = new RegionMap (universalId, document, this);
mTable->verticalHeader()->hide();
mTable->horizontalHeader()->hide();
setWidget (mRegionMap);
mTable->setSelectionMode (QAbstractItemView::ExtendedSelection);
mTable->setModel (document.getData().getTableModel (universalId));
mTable->resizeColumnsToContents();
mTable->resizeRowsToContents();
setWidget (mTable);
connect (mRegionMap, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)),
this, SLOT (editRequest (const CSMWorld::UniversalId&, const std::string&)));
}
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"
class QTableView;
class QAction;
namespace CSMDoc
{
@ -12,15 +12,23 @@ namespace CSMDoc
namespace CSVWorld
{
class RegionMap;
class RegionMapSubView : public CSVDoc::SubView
{
QTableView *mTable;
Q_OBJECT
RegionMap *mRegionMap;
public:
RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document);
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 <sstream>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include "../../model/doc/document.hpp"
#include "../../model/world/cellselection.hpp"
#include "../filter/filterbox.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));
SceneToolbar *toolbar = new SceneToolbar (48, this);
SceneToolbar *toolbar = new SceneToolbar (48+6, this);
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
mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this);
@ -83,7 +92,38 @@ void CSVWorld::SceneSubView::setStatusBar (bool show)
mBottom->setStatusBar (show);
}
void CSVWorld::SceneSubView::useHint (const std::string& hint)
{
mScene->useViewHint (hint);
}
void CSVWorld::SceneSubView::closeRequest()
{
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;
namespace CSMWorld
{
class CellSelection;
}
namespace CSMDoc
{
class Document;
@ -38,9 +43,13 @@ namespace CSVWorld
virtual void setStatusBar (bool show);
virtual void useHint (const std::string& hint);
private slots:
void closeRequest();
void cellSelectionChanged (const CSMWorld::CellSelection& selection);
};
}

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

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

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

@ -8,7 +8,7 @@
#include "scenetoolbar.hpp"
CSVWorld::SceneToolMode::SceneToolMode (SceneToolbar *parent)
: SceneTool (parent), mButtonSize (parent->getButtonSize())
: SceneTool (parent), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize())
{
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);
button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed));
button->setIconSize (QSize (mIconSize, mIconSize));
button->setFixedSize (mButtonSize, mButtonSize);
mLayout->addWidget (button);

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

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

@ -26,9 +26,9 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
layout->insertWidget (0, mTable =
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;
@ -48,7 +48,7 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
mTable->selectionSizeUpdate();
mTable->viewport()->installEventFilter(this);
mBottom->installEventFilter(this);
filterBox->installEventFilter(this);
mFilterBox->installEventFilter(this);
if (mBottom->canCreateAndDelete())
{
@ -63,17 +63,12 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
connect (mBottom, SIGNAL (requestFocus (const std::string&)),
mTable, SLOT (requestFocus (const std::string&)));
connect (filterBox,
connect (mFilterBox,
SIGNAL (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)));
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)
@ -97,6 +92,15 @@ void CSVWorld::TableSubView::setStatusBar (bool 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)
{
emit cloneRequest(toClone.getId(), toClone.getType());
@ -113,7 +117,7 @@ void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::Univers
filterSource.push_back(pair);
}
emit createFilterRequest(filterSource, action);
mFilterBox->createFilterRequest(filterSource, action);
}
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);
if (handled)
{
emit useFilterRequest(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId());
mFilterBox->setRecordFilter(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId());
}
return handled;
}

@ -17,6 +17,11 @@ namespace CSMDoc
class Document;
}
namespace CSVFilter
{
class FilterBox;
}
namespace CSVWorld
{
class Table;
@ -29,6 +34,7 @@ namespace CSVWorld
Table *mTable;
TableBottomBox *mBottom;
CSVFilter::FilterBox *mFilterBox;
public:
@ -41,15 +47,14 @@ namespace CSVWorld
virtual void setStatusBar (bool show);
virtual void useHint (const std::string& hint);
protected:
bool eventFilter(QObject* object, QEvent *event);
signals:
void cloneRequest(const std::string&,
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:

@ -108,7 +108,11 @@ void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemM
{
NastyTableModelHack hack (*model);
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)

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

@ -134,10 +134,6 @@ namespace MWClass
getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr), "",
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)
getInventoryStore(ptr).autoEquip(ptr);
}
@ -811,6 +807,11 @@ namespace MWClass
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::fMaxWalkSpeedCreature;
const ESM::GameSetting *Creature::fEncumberedMoveEffect;

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

@ -365,13 +365,7 @@ namespace MWClass
// store
ptr.getRefData().setCustomData (data.release());
// 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, gold, ptr);
getInventoryStore(ptr).autoEquip(ptr);
getInventoryStore(ptr).autoEquip(ptr);
}
}
@ -818,7 +812,13 @@ namespace MWClass
return boost::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction("#{sActorInCombat}"));
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak))
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));
}
MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)
@ -1294,6 +1294,20 @@ namespace MWClass
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::fMaxWalkSpeed;
const ESM::GameSetting *Npc::fEncumberedMoveEffect;

@ -166,6 +166,10 @@ namespace MWClass
virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
const;
///< 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;
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;

@ -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)
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWBase::MechanicsManager::PersuasionType type;
if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire;
else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate;
else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt;
else if (sender == mBribe10Button)
{
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 10, player);
type = MWBase::MechanicsManager::PT_Bribe10;
}
else if (sender == mBribe100Button)
{
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 100, player);
type = MWBase::MechanicsManager::PT_Bribe100;
}
else /*if (sender == mBribe1000Button)*/
{
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 1000, player);
type = MWBase::MechanicsManager::PT_Bribe1000;
}
MWBase::Environment::get().getDialogueManager()->persuade(type);

@ -131,7 +131,22 @@ namespace MWGui
const ESM::Class *cls =
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;
mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast<std::string>(level));

@ -9,14 +9,14 @@
#include <OgreViewport.h>
#include <OgreHardwarePixelBuffer.h>
#include <openengine/ogre/fader.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/inputmanager.hpp"
#include "backgroundimage.hpp"
namespace MWGui
{
@ -32,28 +32,13 @@ namespace MWGui
{
getWidget(mLoadingText, "LoadingText");
getWidget(mProgressBar, "ProgressBar");
getWidget(mBackgroundImage, "BackgroundImage");
mProgressBar->setScrollViewPage(1);
mBackgroundMaterial = Ogre::MaterialManager::getSingleton().create("BackgroundMaterial", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
mBackgroundMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
mBackgroundMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false);
mBackgroundMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("");
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);
mBackgroundImage = MyGUI::Gui::getInstance().createWidgetReal<BackgroundImage>("ImageBox", 0,0,1,1,
MyGUI::Align::Stretch, "Menu");
setVisible(false);
}
void LoadingScreen::setLabel(const std::string &label)
@ -63,18 +48,25 @@ namespace MWGui
LoadingScreen::~LoadingScreen()
{
delete mRectangle;
}
void LoadingScreen::setVisible(bool visible)
{
WindowBase::setVisible(visible);
mBackgroundImage->setVisible(visible);
}
void LoadingScreen::onResChange(int w, int h)
{
setCoord(0,0,w,h);
mBackgroundImage->setCoord(MyGUI::IntCoord(0,0,w,h));
}
void LoadingScreen::loadingOn()
{
// Early-out if already on
if (mRectangle->getVisible())
if (mMainWidget->getVisible())
return;
// 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();
mWindow->copyContentsToMemory(texture->getBuffer()->lock(Ogre::Image::Box(0,0,width,height), Ogre::HardwareBuffer::HBL_DISCARD));
texture->getBuffer()->unlock();
mBackgroundImage->setImageTexture(texture->getName());
mBackgroundImage->setBackgroundImage(texture->getName(), false, false);
}
setVisible(true);
@ -149,9 +141,10 @@ namespace MWGui
{
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
std::cerr << "No loading screens found!" << std::endl;
@ -237,8 +230,6 @@ namespace MWGui
mWindow->update(false);
mRectangle->setVisible(false);
// resume 3d rendering
mSceneMgr->clearSpecialCaseRenderQueues();
mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE);

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

@ -14,6 +14,8 @@
#include "savegamedialog.hpp"
#include "confirmationdialog.hpp"
#include "imagebutton.hpp"
#include "backgroundimage.hpp"
namespace MWGui
{
@ -132,34 +134,14 @@ namespace MWGui
void MainMenu::showBackground(bool show)
{
if (mBackground)
{
MyGUI::Gui::getInstance().destroyWidget(mBackground);
mBackground = NULL;
}
if (show)
if (show && !mBackground)
{
if (!mBackground)
{
mBackground = MyGUI::Gui::getInstance().createWidgetReal<MyGUI::ImageBox>("ImageBox", 0,0,1,1,
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");
}
mBackground = MyGUI::Gui::getInstance().createWidgetReal<BackgroundImage>("ImageBox", 0,0,1,1,
MyGUI::Align::Stretch, "Menu");
mBackground->setBackgroundImage("textures\\menu_morrowind.dds");
}
if (mBackground)
mBackground->setVisible(show);
}
void MainMenu::updateMenu()
@ -174,6 +156,7 @@ namespace MWGui
MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState();
showBackground(state == MWBase::StateManager::State_NoGame);
mVersionText->setVisible(state == MWBase::StateManager::State_NoGame);
std::vector<std::string> buttons;

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

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

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

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

@ -29,6 +29,7 @@
#include "aicombat.hpp"
#include "aifollow.hpp"
#include "aipersue.hpp"
namespace
{
@ -175,14 +176,16 @@ namespace MWMechanics
adjustMagicEffects (ptr);
if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats())
calculateDynamicStats (ptr);
calculateCreatureStatModifiers (ptr, duration);
// AI
if(MWBase::Environment::get().getMechanicsManager()->isAIActive())
{
CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
//engage combat or not?
CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr);
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
//engage combat or not?
if(ptr != player && !creatureStats.isHostile())
{
ESM::Position playerpos = player.getRefData().getPosition();
@ -196,9 +199,8 @@ namespace MWMechanics
{
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 >= 90 && d <= 2000)
|| (fight >= 80 && d <= 1000)
@ -206,15 +208,20 @@ namespace MWMechanics
|| (fight >= 70 && disp <= 35 && d <= 1000)
|| (fight >= 60 && disp <= 30 && d <= 1000)
|| (fight >= 50 && disp == 0)
|| (fight >= 40 && disp <= 10 && d <= 500) )
&& LOS
|| (fight >= 40 && disp <= 10 && d <= 500)
)
{
creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr()));
creatureStats.setHostile(true);
bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player)
&& 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);
}
@ -539,6 +546,8 @@ namespace MWMechanics
ref.getPtr().getCellRef().mPos = ipos;
// 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
creatureStats.mSummonedCreatures.insert(std::make_pair(it->first,
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()

@ -42,6 +42,8 @@ namespace MWMechanics
void updateEquippedLight (const MWWorld::Ptr& ptr, float duration);
void updateCrimePersuit (const MWWorld::Ptr& ptr, float duration);
public:
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();
bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY;

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

@ -1,28 +1,34 @@
#include "aifollow.hpp"
#include <OgreMath.h>
#include <iostream>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp"
#include "movement.hpp"
#include <OgreMath.h>
#include "steering.hpp"
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)
: 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)
{
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;
mStuckTimer = mStuckTimer + duration;
@ -30,22 +36,25 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
ESM::Position pos = actor.getRefData().getPosition();
if(mTotalTime > mDuration && mDuration != 0)
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(!mAlwaysFollow)
{
if(actor.getCell()->isExterior())
{
if(mCellId == "")
return true;
}
else
if(mTotalTime > mDuration && mDuration != 0)
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(mCellId == actor.getCell()->getCell()->mName)
return true;
if(actor.getCell()->isExterior())
{
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();
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.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ)
> 100*100)

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

@ -19,7 +19,8 @@ namespace MWMechanics
TypeIdEscort = 2,
TypeIdFollow = 3,
TypeIdActivate = 4,
TypeIdCombat = 5
TypeIdCombat = 5,
TypeIdPersue = 6
};
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
{
return mDone;

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

@ -18,9 +18,10 @@
namespace MWMechanics
{
// 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_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):
mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat)
@ -35,7 +36,8 @@ namespace MWMechanics
, mPrevY(0)
, mWalkState(State_Norm)
, mStuckCount(0)
, mEvadeCount(0)
, mEvadeDuration(0)
, mStuckDuration(0)
, mSaidGreeting(false)
{
for(unsigned short counter = 0; counter < mIdle.size(); counter++)
@ -69,6 +71,7 @@ namespace MWMechanics
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)
{
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
@ -102,20 +105,21 @@ namespace MWMechanics
ESM::Position pos = actor.getRefData().getPosition();
// Once off initialization to discover & store allowed node points for this actor.
if(!mStoredAvailableNodes)
{
mStoredAvailableNodes = true;
mPathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
mCellX = actor.getCell()->getCell()->mData.mX;
mCellY = actor.getCell()->getCell()->mData.mY;
// TODO: If there is no path does this actor get stuck forever?
if(!mPathgrid)
mDistance = 0;
else if(mPathgrid->mPoints.empty())
mDistance = 0;
if(mDistance)
if(mDistance) // A distance value is initially passed into the constructor.
{
mXCell = 0;
mYCell = 0;
@ -125,14 +129,18 @@ namespace MWMechanics
mYCell = mCellY * ESM::Land::REAL_SIZE;
}
// convert npcPos to local (i.e. cell) co-ordinates
Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos);
npcPos[0] = npcPos[0] - mXCell;
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++)
{
Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, mPathgrid->mPoints[counter].mY,
mPathgrid->mPoints[counter].mZ);
Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX,
mPathgrid->mPoints[counter].mY,
mPathgrid->mPoints[counter].mZ);
if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance)
mAllowedNodes.push_back(mPathgrid->mPoints[counter]);
}
@ -143,18 +151,22 @@ namespace MWMechanics
unsigned int index = 0;
for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++)
{
Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY,
mAllowedNodes[counterThree].mZ);
Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX,
mAllowedNodes[counterThree].mY,
mAllowedNodes[counterThree].mZ);
float tempDist = npcPos.squaredDistance(nodePos);
if(tempDist < closestNode)
index = counterThree;
}
mCurrentNode = mAllowedNodes[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())
mDistance = 0;
@ -162,7 +174,7 @@ namespace MWMechanics
if(mDistance && (mCellX != actor.getCell()->getCell()->mData.mX || mCellY != actor.getCell()->getCell()->mData.mY))
mDistance = 0;
if(mChooseAction)
if(mChooseAction) // Initially set true by the constructor.
{
mPlayedIdle = 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
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
@ -222,6 +235,14 @@ namespace MWMechanics
float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance(
Ogre::Vector3(actor.getRefData().getPosition().pos));
if(mWalking && playerDist <= helloDistance)
{
stopWalking(actor);
mMoveNow = false;
mWalking = false;
mWalkState = State_Norm;
}
if (!mSaidGreeting)
{
// TODO: check if actor is aware / has line of sight
@ -263,16 +284,24 @@ namespace MWMechanics
dest.mY = destNodePos[1] + mYCell;
dest.mZ = destNodePos[2];
// actor position is already in world co-ordinates
ESM::Pathgrid::Point start;
start.mX = pos.pos[0];
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
// don't take shortcuts for wandering
mPathFinder.buildPath(start, dest, actor.getCell(), false);
if(mPathFinder.isPathConstructed())
{
// 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];
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
mAllowedNodes.push_back(mCurrentNode);
@ -298,15 +327,21 @@ namespace MWMechanics
}
else
{
/* 1 n
/* f t
* State_Norm <---> State_CheckStuck --> State_Evade
* ^ ^ | ^ | ^ | |
* | | | | | | | |
* | +---+ +---+ +---+ | m
* | any < n < m |
* | +---+ +---+ +---+ | u
* | 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)
{
case State_Norm:
@ -322,30 +357,33 @@ namespace MWMechanics
if(!samePosition)
{
mWalkState = State_Norm;
// to do this properly need yet another variable, simply don't clear for now
//mStuckCount = 0;
mStuckDuration = 0;
break;
}
else
{
// consider stuck only if position unchanges consecutively
if((mStuckCount++ % COUNT_BEFORE_STUCK) == 0)
mStuckDuration += duration;
// consider stuck only if position unchanges for a period
if(mStuckDuration > DURATION_SAME_SPOT)
{
mWalkState = State_Evade;
// NOTE: mStuckCount is purposely not cleared here
mStuckDuration = 0;
mStuckCount++;
}
else
break; // still in the same state, but counter got incremented
break; // still in the same state, but duration added to timer
}
}
/* FALL THROUGH */
case State_Evade:
{
if(mEvadeCount++ < COUNT_EVADE)
mEvadeDuration += duration;
if(mEvadeDuration < DURATION_TO_EVADE)
break;
else
{
mWalkState = State_Norm; // tried to evade, assume all is ok and start again
// NOTE: mStuckCount is purposely not cleared here
mEvadeCount = 0;
mEvadeDuration = 0;
}
}
/* NO DEFAULT CASE */
@ -353,7 +391,7 @@ namespace MWMechanics
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
actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
@ -363,13 +401,16 @@ namespace MWMechanics
}
else
{
// normal walk forward
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])));
}
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;
mStuckCount = 0;

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

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

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

@ -1,5 +1,6 @@
#include "mechanicsmanagerimp.hpp"
#include "npcstats.hpp"
#include "../mwworld/esmstore.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)
{
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;
bool reported=false;
for (Actors::PtrControllerMap::const_iterator it = mActors.begin(); it != mActors.end(); ++it)
{
if (it->first != ptr &&
MWBase::Environment::get().getWorld()->getLOS(ptr, it->first) &&
awarenessCheck(ptr, it->first))
const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
// What amount of alarm did this crime generate?
int alarm;
if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
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?
// (not sure, is > 0 correct?)
if (it->first.getClass().getCreatureStats(it->first).getAiSetting(CreatureStats::AI_Alarm).getModified() > 0)
// Will the witness report the crime?
if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm)
{
// TODO: stats.setAlarmed(true) on NPCs within earshot
// fAlarmRadius ?
reported=true;
break;
reported = true;
int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId();
// 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)
reportCrime(ptr, victim, type, arg);
return reported;
@ -838,6 +879,7 @@ namespace MWMechanics
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>();
// Bounty for each type of crime
if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
arg = store.find("iCrimeTresspass")->getInt();
@ -850,32 +892,10 @@ namespace MWMechanics
else if (type == OT_Theft)
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}");
ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty()
+ 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 (!victim.isEmpty() && victim.getClass().isNpc())
{
@ -887,8 +907,6 @@ namespace MWMechanics
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)

@ -29,6 +29,7 @@ MWMechanics::NpcStats::NpcStats()
, mLevelProgress(0)
, mDisposition(0)
, mReputation(0)
, mCrimeId(-1)
, mWerewolfKills (0)
, mProfit(0)
, mTimeToStartDrowning(20.0)
@ -340,6 +341,16 @@ void MWMechanics::NpcStats::setReputation(int 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
{
if (rank<0 || rank>=10)
@ -441,6 +452,8 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const
mWerewolfSkill[i].writeState (state.mSkills[i].mWerewolf);
}
state.mCrimeId = mCrimeId;
state.mBounty = mBounty;
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);
}
mCrimeId = state.mCrimeId;
mBounty = state.mBounty;
mReputation = state.mReputation;
mWerewolfKills = state.mWerewolfKills;

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

@ -1,7 +1,5 @@
#include "pathfinding.hpp"
#include <map>
#include "OgreMath.h"
#include "OgreVector3.h"
@ -37,19 +35,37 @@ namespace
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())
return -1;
float distanceBetween = distance(grid->mPoints[0], x, y, z);
float distanceBetween = distanceSquared(grid->mPoints[0], pos);
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++)
{
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;
}
}
@ -57,96 +73,39 @@ namespace
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)
{
std::list<ESM::Pathgrid::Point> path;
while(graph[lastNode].parent != -1)
{
//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)
// Chooses a reachable end pathgrid point. start is assumed reachable.
std::pair<int, bool> getClosestReachablePoint(const ESM::Pathgrid* grid,
const MWWorld::CellStore *cell,
Ogre::Vector3 pos, int start)
{
std::vector<Node> graph;
for(unsigned int i = 0; i < pathgrid->mPoints.size(); i++)
{
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;
if(!grid || grid->mPoints.empty())
return std::pair<int, bool> (-1, false);
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();
openset.pop_front();
if(current == goal) break;
closedset.push_back(current);
for(int j = 0;j<graph[current].edges.size();j++)
float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos);
if(potentialDistBetween < distanceBetween)
{
//int next = graph[current].edges[j].destination
if(std::find(closedset.begin(),closedset.end(),graph[current].edges[j].destination) == closedset.end())
// found a closer one
distanceBetween = potentialDistBetween;
closestIndex = counter;
if (cell->isPointConnected(start, counter))
{
int dest = graph[current].edges[j].destination;
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);
}
}
closestReachableIndex = counter;
}
}
}
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()
: mIsPathConstructed(false),
mIsGraphConstructed(false),
mPathgrid(NULL),
mCell(NULL)
{
}
@ -166,173 +125,138 @@ namespace MWMechanics
mIsPathConstructed = false;
}
void PathFinder::buildPathgridGraph(const ESM::Pathgrid* pathGrid)
{
mGraph.clear();
mGScore.resize(pathGrid->mPoints.size(),-1);
mFScore.resize(pathGrid->mPoints.size(),-1);
Node defaultNode;
defaultNode.label = -1;
defaultNode.parent = -1;
mGraph.resize(pathGrid->mPoints.size(),defaultNode);
for(unsigned int i = 0; i < pathGrid->mPoints.size(); i++)
{
Node node;
node.label = i;
node.parent = -1;
mGraph[i] = 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]);
mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge);
edge.destination = pathGrid->mEdges[i].mV0;
mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge);
}
mIsGraphConstructed = true;
}
void PathFinder::cleanUpAStar()
{
for(int i=0;i<static_cast<int> (mGraph.size());i++)
{
mGraph[i].parent = -1;
mGScore[i] = -1;
mFScore[i] = -1;
}
}
std::list<ESM::Pathgrid::Point> PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell, float yCell)
/*
* NOTE: This method may fail to find a path. The caller must check the
* result before using it. If there is no path the AI routies need to
* implement some other heuristics to reach the target.
*
* NOTE: It may be desirable to simply go directly to the endPoint if for
* example there are no pathgrids in this cell.
*
* NOTE: startPoint & endPoint are in world co-ordinates
*
* Updates mPath using aStarSearch() or ray test (if shortcut allowed).
* mPath consists of pathgrid points, except the last element which is
* endPoint. This may be useful where the endPoint is not on a pathgrid
* point (e.g. combat). However, if the caller has already chosen a
* pathgrid point (e.g. wander) then it may be worth while to call
* pop_back() to remove the redundant entry.
*
* mPathConstructed is set true if successful, false if not
*
* NOTE: co-ordinates must be converted prior to calling getClosestPoint()
*
* |
* | cell
* | +-----------+
* | | |
* | | |
* | | @ |
* | i | j |
* |<--->|<---->| |
* | +-----------+
* | k
* |<---------->| world
* +-----------------------------
*
* 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
*/
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint,
const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell,
bool allowShortcuts)
{
cleanUpAStar();
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;
mPath.clear();
while(!openset.empty())
if(allowShortcuts)
{
current = openset.front();
openset.pop_front();
if(current == goal) break;
closedset.push_back(current);
for(int j = 0;j<static_cast<int> (mGraph[current].edges.size());j++)
// if there's a ray cast hit, can't take a direct path
if(!MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ,
endPoint.mX, endPoint.mY, endPoint.mZ))
{
//int next = mGraph[current].edges[j].destination
if(std::find(closedset.begin(),closedset.end(),mGraph[current].edges[j].destination) == closedset.end())
{
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);
}
}
}
mPath.push_back(endPoint);
mIsPathConstructed = true;
return;
}
}
std::list<ESM::Pathgrid::Point> path;
while(mGraph[current].parent != -1)
if(mCell != cell || !mPathgrid)
{
//std::cout << "not empty" << xCell;
ESM::Pathgrid::Point pt = pathGrid->mPoints[current];
pt.mX += xCell;
pt.mY += yCell;
path.push_front(pt);
current = mGraph[current].parent;
mCell = cell;
mPathgrid = MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*mCell->getCell());
}
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];
pt.mX += xCell;
pt.mY += yCell;
path.push_front(pt);
mPath.push_back(endPoint);
mIsPathConstructed = true;
return;
}
return path;
}
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, bool allowShortcuts)
{
mPath.clear();
if(mCell != cell) mIsGraphConstructed = false;
mCell = cell;
if(allowShortcuts)
// NOTE: getClosestPoint expects local co-ordinates
float xCell = 0;
float yCell = 0;
if (mCell->isExterior())
{
if(MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ,
endPoint.mX, endPoint.mY, endPoint.mZ))
allowShortcuts = false;
xCell = mCell->getCell()->mData.mX * ESM::Land::REAL_SIZE;
yCell = mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE;
}
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 =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*mCell->getCell());
float xCell = 0;
float yCell = 0;
std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, cell,
Ogre::Vector3(endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ),
startNode);
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;
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);
mPath = mCell->aStarSearch(startNode, endNode.first);
if(!mPath.empty())
{
mPath.push_back(endPoint);
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
{
mPath.push_back(endPoint);
mIsPathConstructed = true;
}
if(mPath.empty())
mIsPathConstructed = false;
return;
}
float PathFinder::getZAngleToNext(float x, float y) const
{
// This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call
// if otherwise).
// This should never happen (programmers should have an if statement checking
// mIsPathConstructed that prevents this call if otherwise).
if(mPath.empty())
return 0.;
@ -344,6 +268,7 @@ namespace MWMechanics
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)
{
ESM::Pathgrid::Point nextPoint = *mPath.begin();
@ -384,6 +309,7 @@ namespace MWMechanics
return false;
}
// used by AiCombat, see header for the rationale
void PathFinder::syncStart(const std::list<ESM::Pathgrid::Point> &path)
{
if (mPath.size() < 2)
@ -403,4 +329,3 @@ namespace MWMechanics
}
}

@ -34,8 +34,6 @@ namespace MWMechanics
void clearPath();
void buildPathgridGraph(const ESM::Pathgrid* pathGrid);
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, bool allowShortcuts = true);
@ -64,9 +62,10 @@ namespace MWMechanics
return mPath;
}
//When first point of newly created path is the nearest to actor point, then
//the cituation can occure when this point is undesirable (if the 2nd point of new path == the 1st point of old path)
//This functions deletes that point.
// When first point of newly created path is the nearest to actor point,
// then a situation can occure when this point is undesirable
// (if the 2nd point of new path == the 1st point of old path)
// This functions deletes that point.
void syncStart(const std::list<ESM::Pathgrid::Point> &path);
void addPointToPath(ESM::Pathgrid::Point &point)
@ -76,30 +75,11 @@ namespace MWMechanics
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;
std::list<ESM::Pathgrid::Point> mPath;
bool mIsGraphConstructed;
const ESM::Pathgrid *mPathgrid;
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");
if (!tex.isNull())
{
std::map <std::string, std::vector<Ogre::uint32> >::iterator anIter;
// 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;
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))
+ Math::Sqr((texV + my*(sFogOfWarResolution-1)) - v*(sFogOfWarResolution-1));
uint32 clr = mBuffers[texName][i];
uint32 clr = aBuffer[i];
uint8 alpha = (clr >> 24);
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;
}
}
// 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();
}
}

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

@ -21,6 +21,7 @@
#include "../mwbase/scriptmanager.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp"
@ -802,6 +803,7 @@ namespace MWScript
{
MWBase::World* world = MWBase::Environment::get().getWorld();
world->goToJail();
MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId();
}
};
@ -812,7 +814,7 @@ namespace MWScript
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
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();
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);
mState = State_Loaded;
mPathgridGraph.load(mCell);
}
}
@ -680,4 +681,14 @@ namespace MWWorld
{
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 "cellreflist.hpp"
#include "../mwmechanics/pathgrid.hpp"
namespace ESM
{
struct CellState;
@ -141,6 +143,10 @@ namespace MWWorld
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:
template<class Functor, class List>
@ -166,6 +172,8 @@ namespace MWWorld
///< Make case-adjustments to \a ref and insert it into the respective container.
///
/// Invalid \a ref objects are silently dropped.
MWMechanics::PathgridGraph mPathgridGraph;
};
template<>

@ -396,4 +396,14 @@ namespace MWWorld
void Class::readAdditionalState (const MWWorld::Ptr& ptr, const 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.
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;
}
Ogre::Vector3 oldPosition = newPosition;
// 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 may modify newPosition
if((canWalk || isBipedal || isNpc) && stepMove(colobj, newPosition, velocity, remainingTime, engine))
isOnGround = !(newPosition.z < waterlevel || isFlying); // Only on the ground if there's gravity
// NOTE: stepMove modifies newPosition if successful
if(stepMove(colobj, newPosition, velocity, remainingTime, engine))
{
// 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
{
// Can't move this way, try to find another spot along the plane

@ -28,9 +28,11 @@ namespace MWWorld
: mCellStore(0),
mLastKnownExteriorPosition(0,0,0),
mAutoMove(false),
mForwardBackward (0),
mForwardBackward(0),
mTeleported(false),
mMarkedCell(NULL)
mMarkedCell(NULL),
mCurrentCrimeId(-1),
mPayedCrimeId(-1)
{
mPlayer.mBase = player;
mPlayer.mRef.mRefID = "player";
@ -194,6 +196,9 @@ namespace MWWorld
mPlayer.save (player.mObject);
player.mCellId = mCellStore->getCell()->getCellId();
player.mCurrentCrimeId = mCurrentCrimeId;
player.mPayedCrimeId = mPayedCrimeId;
player.mBirthsign = mSign;
player.mLastKnownExteriorPosition[0] = mLastKnownExteriorPosition.x;
@ -239,6 +244,9 @@ namespace MWWorld
!world.getStore().get<ESM::BirthSign>().search (player.mBirthsign))
throw std::runtime_error ("invalid player state record (birthsign)");
mCurrentCrimeId = player.mCurrentCrimeId;
mPayedCrimeId = player.mPayedCrimeId;
mSign = player.mBirthsign;
mLastKnownExteriorPosition.x = player.mLastKnownExteriorPosition[0];
@ -274,4 +282,19 @@ namespace MWWorld
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;
int mForwardBackward;
bool mTeleported;
int mCurrentCrimeId; // the id assigned witnesses
int mPayedCrimeId; // the last id payed off (0 bounty)
public:
Player(const ESM::NPC *player, const MWBase::World& world);
@ -63,15 +67,12 @@ namespace MWWorld
MWWorld::Ptr getPlayer();
void setBirthSign(const std::string &sign);
const std::string &getBirthSign() const;
void setDrawState (MWMechanics::DrawState_ state);
bool getAutoMove() const;
MWMechanics::DrawState_ getDrawState(); /// \todo constness
bool getAutoMove() const;
void setAutoMove (bool enable);
void setLeftRight (int value);
@ -94,6 +95,10 @@ namespace MWWorld
void write (ESM::ESMWriter& writer) const;
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

@ -9,6 +9,7 @@
#include <OgreSceneNode.h>
#include <libs/openengine/bullet/trace.h>
#include <libs/openengine/bullet/physic.hpp>
#include <components/bsa/bsa_archive.hpp>
@ -1711,11 +1712,43 @@ namespace MWWorld
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
{
RefData &refdata = ptr.getRefData();
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)
@ -2071,6 +2104,12 @@ namespace MWWorld
{
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;
}
// TODO: Sleep the player
std::vector<std::string> buttons;
buttons.push_back("#{sOk}");
MWBase::Environment::get().getWindowManager()->messageBox(message, buttons);

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

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

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

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

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

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

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

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

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

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

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

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

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

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