mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-21 06:53:53 +00:00
Merge remote-tracking branch 'upstream/master' into wizard
This commit is contained in:
commit
a2c129f655
109 changed files with 2506 additions and 749 deletions
|
@ -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;
|
||||
|
|
60
apps/opencs/model/world/cellcoordinates.cpp
Normal file
60
apps/opencs/model/world/cellcoordinates.cpp
Normal file
|
@ -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();
|
||||
}
|
42
apps/opencs/model/world/cellcoordinates.hpp
Normal file
42
apps/opencs/model/world/cellcoordinates.hpp
Normal file
|
@ -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
|
83
apps/opencs/model/world/cellselection.cpp
Normal file
83
apps/opencs/model/world/cellselection.cpp
Normal file
|
@ -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);
|
||||
}
|
57
apps/opencs/model/world/cellselection.hpp
Normal file
57
apps/opencs/model/world/cellselection.hpp
Normal file
|
@ -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;
|
||||
beginInsertColumns (QModelIndex(), 0, std::abs (diff)-1);
|
||||
mMin = CellCoordinates (size.first.getX(), mMin.getY());
|
||||
endInsertColumns();
|
||||
}
|
||||
else if (diff>0)
|
||||
{
|
||||
beginRemoveColumns (QModelIndex(), 0, diff-1);
|
||||
mMin.first = size.first.first;
|
||||
endRemoveColumns();
|
||||
}
|
||||
}
|
||||
|
||||
if (int diff = size.first.getY() - mMin.getY())
|
||||
{
|
||||
int diff = size.first.second - mMin.second;
|
||||
|
||||
if (diff<0)
|
||||
{
|
||||
beginInsertRows (QModelIndex(), 0, -diff-1);
|
||||
mMin.second = size.first.second;
|
||||
beginInsertRows (QModelIndex(), 0, std::abs (diff)-1);
|
||||
mMin = CellCoordinates (mMin.getX(), size.first.getY());
|
||||
endInsertRows();
|
||||
}
|
||||
else if (diff>0)
|
||||
{
|
||||
beginRemoveRows (QModelIndex(), 0, diff-1);
|
||||
mMin.second = size.first.second;
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int diff = size.second.first - mMax.first;
|
||||
|
||||
if (diff>0)
|
||||
if (int diff = size.second.getX() - mMax.getX())
|
||||
{
|
||||
int columns = columnCount();
|
||||
|
||||
if (diff>0)
|
||||
beginInsertColumns (QModelIndex(), columns, columns+diff-1);
|
||||
mMax.first = size.second.first;
|
||||
else
|
||||
beginRemoveColumns (QModelIndex(), columns+diff, columns-1);
|
||||
|
||||
mMax = CellCoordinates (size.second.getX(), mMax.getY());
|
||||
endInsertColumns();
|
||||
}
|
||||
else if (diff<0)
|
||||
{
|
||||
int columns = columnCount();
|
||||
beginRemoveColumns (QModelIndex(), columns+diff, columns-1);
|
||||
mMax.first = size.second.first;
|
||||
endRemoveColumns();
|
||||
}
|
||||
}
|
||||
|
||||
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.getX()<min.getX())
|
||||
min = CellCoordinates (index.getX(), min.getY());
|
||||
else if (index.getX()>=max.getX())
|
||||
max = CellCoordinates (index.getX()+1, max.getY());
|
||||
|
||||
if (index.second<min.second)
|
||||
min.second = index.second;
|
||||
else if (index.second>=max.second)
|
||||
max.second = index.second + 1;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -194,10 +194,3 @@ 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)));
|
||||
|
||||
connect(this, SIGNAL(useFilterRequest(const std::string&)), editWidget, SLOT(useFilterRequest(const std::string&)));
|
||||
}
|
||||
|
||||
void CSVFilter::RecordFilterBox::setFilter (const std::string& filter)
|
||||
{
|
||||
mEdit->clear();
|
||||
mEdit->setText (QString::fromUtf8 (filter.c_str()));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
void setFilter (const std::string& filter);
|
||||
|
||||
void useFilterRequest(const std::string& idOfFilter);
|
||||
|
||||
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);
|
||||
void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
|
||||
Qt::DropAction action);
|
||||
void useFilterRequest(const std::string& idOfFilter);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
346
apps/opencs/view/world/regionmap.cpp
Normal file
346
apps/opencs/view/world/regionmap.cpp
Normal file
|
@ -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());
|
||||
}
|
84
apps/opencs/view/world/regionmap.hpp
Normal file
84
apps/opencs/view/world/regionmap.hpp
Normal file
|
@ -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);
|
||||
|
||||
|
@ -27,3 +27,8 @@ 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);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
|
63
apps/openmw/mwgui/backgroundimage.cpp
Normal file
63
apps/openmw/mwgui/backgroundimage.cpp
Normal file
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
37
apps/openmw/mwgui/backgroundimage.hpp
Normal file
37
apps/openmw/mwgui/backgroundimage.hpp
Normal file
|
@ -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,6 +131,21 @@ namespace MWGui
|
|||
const ESM::Class *cls =
|
||||
world->getStore().get<ESM::Class>().find(playerData->mClass);
|
||||
|
||||
// Vanilla uses thief.dds for custom classes. A player with a custom class
|
||||
// doesn't have mId set, see mwworld/esmstore.hpp where it is initialised as
|
||||
// "$dynamic0". This check should resolve bug #1260.
|
||||
// Choosing Stealth specialization and Speed/Agility as attributes.
|
||||
if(world->getStore().get<ESM::Class>().isDynamic(cls->mId))
|
||||
{
|
||||
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;
|
||||
|
|
|
@ -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("");
|
||||
mBackgroundImage = MyGUI::Gui::getInstance().createWidgetReal<BackgroundImage>("ImageBox", 0,0,1,1,
|
||||
MyGUI::Align::Stretch, "Menu");
|
||||
|
||||
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);
|
||||
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)
|
||||
if (show && !mBackground)
|
||||
{
|
||||
MyGUI::Gui::getInstance().destroyWidget(mBackground);
|
||||
mBackground = NULL;
|
||||
}
|
||||
if (show)
|
||||
{
|
||||
if (!mBackground)
|
||||
{
|
||||
mBackground = MyGUI::Gui::getInstance().createWidgetReal<MyGUI::ImageBox>("ImageBox", 0,0,1,1,
|
||||
mBackground = MyGUI::Gui::getInstance().createWidgetReal<BackgroundImage>("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->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)
|
||||
)
|
||||
{
|
||||
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,28 +1,34 @@
|
|||
#include "aifollow.hpp"
|
||||
#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 "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
#include "movement.hpp"
|
||||
#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,6 +36,8 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
|
|||
|
||||
ESM::Position pos = actor.getRefData().getPosition();
|
||||
|
||||
if(!mAlwaysFollow)
|
||||
{
|
||||
if(mTotalTime > mDuration && mDuration != 0)
|
||||
return true;
|
||||
|
||||
|
@ -48,6 +56,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
|
|||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ESM::Pathgrid::Point dest;
|
||||
dest.mX = target.getRefData().getPosition().pos[0];
|
||||
|
|
|
@ -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();
|
||||
|
|
106
apps/openmw/mwmechanics/aipersue.cpp
Normal file
106
apps/openmw/mwmechanics/aipersue.cpp
Normal file
|
@ -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;
|
||||
}
|
29
apps/openmw/mwmechanics/aipersue.hpp
Normal file
29
apps/openmw/mwmechanics/aipersue.hpp
Normal file
|
@ -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;
|
||||
|
|
|
@ -50,6 +50,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,13 +129,17 @@ 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,
|
||||
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,7 +151,8 @@ namespace MWMechanics
|
|||
unsigned int index = 0;
|
||||
for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++)
|
||||
{
|
||||
Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY,
|
||||
Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX,
|
||||
mAllowedNodes[counterThree].mY,
|
||||
mAllowedNodes[counterThree].mZ);
|
||||
float tempDist = npcPos.squaredDistance(nodePos);
|
||||
if(tempDist < closestNode)
|
||||
|
@ -151,10 +160,13 @@ namespace MWMechanics
|
|||
}
|
||||
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 */
|
||||
|
@ -363,7 +401,10 @@ 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])));
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
// 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");
|
||||
}
|
||||
const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
|
||||
|
||||
// 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)
|
||||
{
|
||||
// TODO: stats.setAlarmed(true) on NPCs within earshot
|
||||
// fAlarmRadius ?
|
||||
reported=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 )
|
||||
{
|
||||
|
||||
// Will the witness report the crime?
|
||||
if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm)
|
||||
{
|
||||
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)
|
||||
// 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::list<ESM::Pathgrid::Point> path;
|
||||
while(graph[lastNode].parent != -1)
|
||||
if(!grid || grid->mPoints.empty())
|
||||
return std::pair<int, bool> (-1, false);
|
||||
|
||||
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++)
|
||||
{
|
||||
//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)
|
||||
float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos);
|
||||
if(potentialDistBetween < distanceBetween)
|
||||
{
|
||||
std::vector<Node> graph;
|
||||
for(unsigned int i = 0; i < pathgrid->mPoints.size(); i++)
|
||||
// found a closer one
|
||||
distanceBetween = potentialDistBetween;
|
||||
closestIndex = counter;
|
||||
if (cell->isPointConnected(start, counter))
|
||||
{
|
||||
Node node;
|
||||
node.label = i;
|
||||
node.parent = -1;
|
||||
graph.push_back(node);
|
||||
}
|
||||
for(unsigned int i = 0; i < pathgrid->mEdges.size(); i++)
|
||||
{
|
||||
Edge edge;
|
||||
edge.destination = pathgrid->mEdges[i].mV1;
|
||||
edge.cost = distance(pathgrid->mPoints[pathgrid->mEdges[i].mV0],pathgrid->mPoints[pathgrid->mEdges[i].mV1]);
|
||||
graph[pathgrid->mEdges[i].mV0].edges.push_back(edge);
|
||||
edge.destination = pathgrid->mEdges[i].mV0;
|
||||
graph[pathgrid->mEdges[i].mV1].edges.push_back(edge);
|
||||
}
|
||||
|
||||
std::vector<float> g_score(pathgrid->mPoints.size(),-1.);
|
||||
std::vector<float> f_score(pathgrid->mPoints.size(),-1.);
|
||||
|
||||
g_score[start] = 0;
|
||||
f_score[start] = distance(pathgrid->mPoints[start],pathgrid->mPoints[goal]);
|
||||
|
||||
std::list<int> openset;
|
||||
std::list<int> closedset;
|
||||
openset.push_back(start);
|
||||
|
||||
int current = -1;
|
||||
|
||||
while(!openset.empty())
|
||||
{
|
||||
current = openset.front();
|
||||
openset.pop_front();
|
||||
|
||||
if(current == goal) break;
|
||||
|
||||
closedset.push_back(current);
|
||||
|
||||
for(int j = 0;j<graph[current].edges.size();j++)
|
||||
{
|
||||
//int next = graph[current].edges[j].destination
|
||||
if(std::find(closedset.begin(),closedset.end(),graph[current].edges[j].destination) == closedset.end())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(start == closestReachableIndex)
|
||||
closestReachableIndex = -1; // couldn't find anyting other than start
|
||||
|
||||
return std::pair<int, bool>
|
||||
(closestReachableIndex, closestReachableIndex == closestIndex);
|
||||
}
|
||||
return reconstructPath(graph,pathgrid,current,xCell,yCell);
|
||||
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
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;
|
||||
|
||||
while(!openset.empty())
|
||||
{
|
||||
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++)
|
||||
{
|
||||
//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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::list<ESM::Pathgrid::Point> path;
|
||||
while(mGraph[current].parent != -1)
|
||||
{
|
||||
//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;
|
||||
}
|
||||
|
||||
if(path.empty())
|
||||
{
|
||||
ESM::Pathgrid::Point pt = pathGrid->mPoints[goal];
|
||||
pt.mX += xCell;
|
||||
pt.mY += yCell;
|
||||
path.push_front(pt);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
|
||||
const MWWorld::CellStore* cell, bool allowShortcuts)
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
mPath.clear();
|
||||
if(mCell != cell) mIsGraphConstructed = false;
|
||||
mCell = cell;
|
||||
|
||||
if(allowShortcuts)
|
||||
{
|
||||
if(MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ,
|
||||
// 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))
|
||||
allowShortcuts = false;
|
||||
{
|
||||
mPath.push_back(endPoint);
|
||||
mIsPathConstructed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!allowShortcuts)
|
||||
if(mCell != cell || !mPathgrid)
|
||||
{
|
||||
const ESM::Pathgrid *pathGrid =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*mCell->getCell());
|
||||
mCell = cell;
|
||||
mPathgrid = MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*mCell->getCell());
|
||||
}
|
||||
|
||||
// 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())
|
||||
{
|
||||
mPath.push_back(endPoint);
|
||||
mIsPathConstructed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: getClosestPoint expects local co-ordinates
|
||||
float xCell = 0;
|
||||
float yCell = 0;
|
||||
|
||||
if (mCell->isExterior())
|
||||
{
|
||||
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)
|
||||
// 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)
|
||||
{
|
||||
if(!mIsGraphConstructed) buildPathgridGraph(pathGrid);
|
||||
std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, cell,
|
||||
Ogre::Vector3(endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ),
|
||||
startNode);
|
||||
|
||||
mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell);//findPath(startNode, endNode, mGraph);
|
||||
// this shouldn't really happen, but just in case
|
||||
if(endNode.first != -1)
|
||||
{
|
||||
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
|
||||
{
|
||||
mPath.push_back(endPoint);
|
||||
mIsPathConstructed = true;
|
||||
}
|
||||
|
||||
if(mPath.empty())
|
||||
mIsPathConstructed = false;
|
||||
}
|
||||
else
|
||||
mIsPathConstructed = false;
|
||||
}
|
||||
else
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
|
334
apps/openmw/mwmechanics/pathgrid.cpp
Normal file
334
apps/openmw/mwmechanics/pathgrid.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
77
apps/openmw/mwmechanics/pathgrid.hpp
Normal file
77
apps/openmw/mwmechanics/pathgrid.hpp
Normal file
|
@ -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);
|
||||
}
|
|
@ -45,6 +45,7 @@ namespace ESM
|
|||
float mTimeToStartDrowning;
|
||||
float mLastDrowningHit;
|
||||
float mLevelHealthBonus;
|
||||
int mCrimeId;
|
||||
|
||||
void load (ESMReader &esm);
|
||||
void save (ESMWriter &esm) const;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue