From f2ff2f298888d32d5dcdb7ae5a18e47f2b07fe6b Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 8 Nov 2014 15:51:45 +1100 Subject: [PATCH 001/441] Render pathgrid points and edges. Can move the points but the edges are not updated. Visibility mask does not work properly because pathgrid points are considered as objects by the physics engine. --- apps/opencs/CMakeLists.txt | 1 + apps/opencs/editor.cpp | 3 + apps/opencs/view/render/cell.cpp | 120 ++++++++++++++++++++++ apps/opencs/view/render/cell.hpp | 14 +++ apps/opencs/view/render/mousestate.cpp | 81 ++++++++++----- apps/opencs/view/render/mousestate.hpp | 2 + apps/opencs/view/render/pathgridpoint.cpp | 38 +++++++ apps/opencs/view/render/pathgridpoint.hpp | 35 +++++++ files/materials/pathgrid_pt.nif | Bin 0 -> 1683 bytes 9 files changed, 270 insertions(+), 24 deletions(-) create mode 100644 apps/opencs/view/render/pathgridpoint.cpp create mode 100644 apps/opencs/view/render/pathgridpoint.hpp create mode 100644 files/materials/pathgrid_pt.nif diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index ec6f802cf..29f93e566 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -83,6 +83,7 @@ opencs_units (view/render opencs_units_noqt (view/render navigation navigation1st navigationfree navigationorbit lighting lightingday lightingnight lightingbright object cell terrainstorage textoverlay overlaymask overlaysystem mousestate + pathgridpoint ) opencs_hdrs_noqt (view/render diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 591667ebb..beab0b607 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -308,6 +308,9 @@ std::auto_ptr CS::Editor::setupGraphics() // for font used in overlays Ogre::Root::getSingleton().addResourceLocation ((mResources / "mygui").string(), "FileSystem", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true); + // for pathgrid point nif + Ogre::Root::getSingleton().addResourceLocation ((mResources / "materials").string(), + "FileSystem", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true); if (!boost::filesystem::exists (mCfgMgr.getCachePath())) boost::filesystem::create_directories (mCfgMgr.getCachePath()); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 75e11cc10..6979a4042 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -10,10 +11,60 @@ #include "../../model/world/idtable.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" +#include "../../model/world/pathgrid.hpp" #include "../world/physicssystem.hpp" #include "elements.hpp" #include "terrainstorage.hpp" +#include "pathgridpoint.hpp" + +// FIXME: redraw edges when pathgrid points are moved, added and deleted +namespace CSVRender +{ + // PLEASE NOTE: pathgrid edge code copied and adapted from mwrender/debugging + static const std::string PG_LINE_MATERIAL = "pathgridLineMaterial"; + static const int POINT_MESH_BASE = 35; + static const std::string DEBUGGING_GROUP = "debugging"; +} + +void CSVRender::Cell::createGridMaterials() +{ + if(Ogre::MaterialManager::getSingleton().getByName(PG_LINE_MATERIAL, DEBUGGING_GROUP).isNull()) + { + Ogre::MaterialPtr lineMatPtr = + Ogre::MaterialManager::getSingleton().create(PG_LINE_MATERIAL, DEBUGGING_GROUP); + lineMatPtr->setReceiveShadows(false); + lineMatPtr->getTechnique(0)->setLightingEnabled(true); + lineMatPtr->getTechnique(0)->getPass(0)->setDiffuse(1,1,0,0); + lineMatPtr->getTechnique(0)->getPass(0)->setAmbient(1,1,0); + lineMatPtr->getTechnique(0)->getPass(0)->setSelfIllumination(1,1,0); + } +} + +void CSVRender::Cell::destroyGridMaterials() +{ + if(!Ogre::MaterialManager::getSingleton().getByName(PG_LINE_MATERIAL, DEBUGGING_GROUP).isNull()) + Ogre::MaterialManager::getSingleton().remove(PG_LINE_MATERIAL); +} + +Ogre::ManualObject *CSVRender::Cell::createPathgridEdge(const std::string &name, + const Ogre::Vector3 &start, const Ogre::Vector3 &end) +{ + Ogre::ManualObject *result = mSceneMgr->createManualObject(name); + + result->begin(PG_LINE_MATERIAL, Ogre::RenderOperation::OT_LINE_LIST); + + Ogre::Vector3 direction = (end - start); + Ogre::Vector3 lineDisplacement = direction.crossProduct(Ogre::Vector3::UNIT_Z).normalisedCopy(); + // move lines up a little, so they will be less covered by meshes/landscape + lineDisplacement = lineDisplacement * POINT_MESH_BASE + Ogre::Vector3(0, 0, 10); + result->position(start + lineDisplacement); + result->position(end + lineDisplacement); + + result->end(); + + return result; +} bool CSVRender::Cell::removeObject (const std::string& id) { @@ -94,10 +145,28 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, esmLand->mLandData->mHeights, mX, mY, 0, worldsize / (verts-1), verts); } } + + addPathgrid(); } CSVRender::Cell::~Cell() { + // destroy manual objects + for(std::map, std::string>::iterator iter = mPgEdges.begin(); + iter != mPgEdges.end(); ++iter) + { + if(mSceneMgr->hasManualObject((*iter).second)) + mSceneMgr->destroyManualObject((*iter).second); + } + destroyGridMaterials(); + Ogre::ResourceGroupManager::getSingleton().destroyResourceGroup(DEBUGGING_GROUP); + + for(std::map::iterator iter (mPgPoints.begin()); + iter!=mPgPoints.end(); ++iter) + { + delete iter->second; + } + mPhysics->removeHeightField(mSceneMgr, mX, mY); for (std::map::iterator iter (mObjects.begin()); @@ -236,3 +305,54 @@ float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const else return -std::numeric_limits::max(); } + +void CSVRender::Cell::addPathgrid() +{ + if(!Ogre::ResourceGroupManager::getSingleton().resourceGroupExists(DEBUGGING_GROUP)) + Ogre::ResourceGroupManager::getSingleton().createResourceGroup(DEBUGGING_GROUP); + createGridMaterials(); + + float worldsize = ESM::Land::REAL_SIZE; + + CSMWorld::SubCellCollection& pathgridCollection = mData.getPathgrids(); + int index = pathgridCollection.searchId(mId); + if(index != -1) + { + const CSMWorld::Pathgrid pathgrid = pathgridCollection.getRecord(index).get(); + + std::vector::const_iterator iter = pathgrid.mPoints.begin(); + for(index = 0; iter != pathgrid.mPoints.end(); ++iter, ++index) + { + std::ostringstream stream; + stream << "Pathgrid_" << mId << "_" << index; + std::string name = stream.str(); + + Ogre::Vector3 pos = + Ogre::Vector3(worldsize*mX+(*iter).mX, worldsize*mY+(*iter).mY, (*iter).mZ); + + mPgPoints.insert(std::make_pair(name, new PathgridPoint(name, mCellNode, pos, mPhysics))); + } + + for(ESM::Pathgrid::EdgeList::const_iterator it = pathgrid.mEdges.begin(); + it != pathgrid.mEdges.end(); + ++it) + { + Ogre::SceneNode *node = mCellNode->createChildSceneNode(); + const ESM::Pathgrid::Edge &edge = *it; + const ESM::Pathgrid::Point &p0 = pathgrid.mPoints[edge.mV0]; + const ESM::Pathgrid::Point &p1 = pathgrid.mPoints[edge.mV1]; + + std::ostringstream stream; + stream << mId << "_" << edge.mV0 << " " << edge.mV1; + std::string name = stream.str(); + + Ogre::ManualObject *line = createPathgridEdge(name, + Ogre::Vector3(worldsize*mX+p0.mX, worldsize*mY+p0.mY, p0.mZ), + Ogre::Vector3(worldsize*mX+p1.mX, worldsize*mY+p1.mY, p1.mZ)); + line->setVisibilityFlags(Element_Pathgrid); + node->attachObject(line); + + mPgEdges.insert(std::make_pair(std::make_pair(edge.mV0, edge.mV1), name)); + } + } +} diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index d38f0c68d..c88b59353 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -17,6 +17,7 @@ namespace Ogre { class SceneManager; class SceneNode; + class ManualObject; } namespace CSMWorld @@ -31,12 +32,16 @@ namespace CSVWorld namespace CSVRender { + class PathgridPoint; + class Cell { CSMWorld::Data& mData; std::string mId; Ogre::SceneNode *mCellNode; std::map mObjects; + std::map mPgPoints; + std::map, std::string> mPgEdges; std::auto_ptr mTerrain; CSVWorld::PhysicsSystem *mPhysics; Ogre::SceneManager *mSceneMgr; @@ -82,6 +87,15 @@ namespace CSVRender bool referenceAdded (const QModelIndex& parent, int start, int end); float getTerrainHeightAt(const Ogre::Vector3 &pos) const; + + private: + + // for drawing pathgrid points & lines + void createGridMaterials(); + void destroyGridMaterials(); + void addPathgrid(); + Ogre::ManualObject *createPathgridEdge(const std::string &name, + const Ogre::Vector3 &start, const Ogre::Vector3 &end); }; } diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index 45e846f74..aa8f1a785 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -206,6 +206,15 @@ namespace CSVRender mCurrentObj = result.first; } + // print some debug info + std::string referenceId = mPhysics->sceneNodeToRefId(result.first); + if(referenceId != "") + std::cout << "result: " << referenceId << std::endl; + else + std::cout << "result: " << result.first << std::endl; + std::cout << " hit pos "+ QString::number(result.second.x).toStdString() + + ", " + QString::number(result.second.y).toStdString() + + ", " + QString::number(result.second.z).toStdString() << std::endl; } break; } @@ -213,15 +222,22 @@ namespace CSVRender { // final placement std::pair planeResult = mousePositionOnPlane(event->pos(), *mPlane); - if(planeResult.first) + if(planeResult.first && mGrabbedSceneNode != "") { - if(mGrabbedSceneNode != "") - { - std::pair planeRes = planeAxis(); - Ogre::Vector3 pos = mOrigObjPos+planeRes.first*mOffset+planeResult.second-mOrigMousePos; - // use the saved scene node name since the physics model has not moved yet - std::string referenceId = mPhysics->sceneNodeToRefId(mGrabbedSceneNode); + std::pair planeRes = planeAxis(); + Ogre::Vector3 pos = mOrigObjPos+planeRes.first*mOffset+planeResult.second-mOrigMousePos; + // use the saved scene node name since the physics model has not moved yet + std::string referenceId = mPhysics->sceneNodeToRefId(mGrabbedSceneNode); + if(QString(referenceId.c_str()).contains(QRegExp("^Pathgrid"))) + { + // move pathgrid point, but don't save yet (need pathgrid + // table feature & its data structure to be completed) + // FIXME: need to signal PathgridPoint object of change + placeObject(mGrabbedSceneNode, pos); + } + else + { mParent->mDocument.getUndoStack().beginMacro (QObject::tr("Move Object")); mParent->mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*mIdTableModel, mIdTableModel->getModelIndex(referenceId, mColIndexPosX), pos.x)); @@ -230,22 +246,22 @@ namespace CSVRender mParent->mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*mIdTableModel, mIdTableModel->getModelIndex(referenceId, mColIndexPosZ), pos.z)); mParent->mDocument.getUndoStack().endMacro(); + } - // FIXME: highlight current object? - //mCurrentObj = mGrabbedSceneNode; // FIXME: doesn't work? - mCurrentObj = ""; // whether the object is selected + // FIXME: highlight current object? + //mCurrentObj = mGrabbedSceneNode; // FIXME: doesn't work? + mCurrentObj = ""; // whether the object is selected - mMouseState = Mouse_Edit; + mMouseState = Mouse_Edit; - // reset states - mCurrentMousePos = Ogre::Vector3(); // mouse pos to use in wheel event - mOrigMousePos = Ogre::Vector3(); // starting pos of mouse in world space - mOrigObjPos = Ogre::Vector3(); // starting pos of object in world space - mGrabbedSceneNode = ""; // id of the object - mOffset = 0.0f; // used for z-axis movement - mOldPos = QPoint(0, 0); // to calculate relative movement of mouse - } - } + // reset states + mCurrentMousePos = Ogre::Vector3(); // mouse pos to use in wheel event + mOrigMousePos = Ogre::Vector3(); // starting pos of mouse in world space + mOrigObjPos = Ogre::Vector3(); // starting pos of object in world space + mGrabbedSceneNode = ""; // id of the object + mOffset = 0.0f; // used for z-axis movement + mOldPos = QPoint(0, 0); // to calculate relative movement of mouse + } break; } case Mouse_Edit: @@ -266,9 +282,9 @@ namespace CSVRender void MouseState::mouseDoubleClickEvent (QMouseEvent *event) { - event->ignore(); - //mPhysics->toggleDebugRendering(mSceneManager); - //mParent->flagAsModified(); + //event->ignore(); + mPhysics->toggleDebugRendering(mSceneManager); + mParent->flagAsModified(); } bool MouseState::wheelEvent (QWheelEvent *event) @@ -401,6 +417,10 @@ namespace CSVRender std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); if(result.first != "") { + std::cout << "result: " << result.first << std::endl; + std::cout << " hit pos "+ QString::number(result.second.x).toStdString() + + ", " + QString::number(result.second.y).toStdString() + + ", " + QString::number(result.second.z).toStdString() << std::endl; // FIXME: is there a better way to distinguish terrain from objects? QString name = QString(result.first.c_str()); if(name.contains(QRegExp("^HeightField"))) @@ -423,7 +443,7 @@ namespace CSVRender std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); if(result.first != "") { - // NOTE: anything not terrain is assumed to be an object + // NOTE: anything not terrain is assumed to be an object, e.g pathgrid points QString name = QString(result.first.c_str()); if(!name.contains(QRegExp("^HeightField"))) { @@ -460,4 +480,17 @@ namespace CSVRender { return mParent->getCamera()->getViewport(); } + + void MouseState::placeObject(const std::string sceneNode, const Ogre::Vector3 &pos) + { + mSceneManager->getSceneNode(sceneNode)->setPosition(pos); + + // update physics + std::string refId = mPhysics->sceneNodeToRefId(sceneNode); + + mPhysics->replaceObject(sceneNode, 1, pos, Ogre::Quaternion::IDENTITY); + + // update all SceneWidgets and their SceneManagers + updateSceneWidgets(); + } } diff --git a/apps/opencs/view/render/mousestate.hpp b/apps/opencs/view/render/mousestate.hpp index 27907bb33..36d04ef6b 100644 --- a/apps/opencs/view/render/mousestate.hpp +++ b/apps/opencs/view/render/mousestate.hpp @@ -82,6 +82,8 @@ namespace CSVRender std::pair planeAxis(); void updateSceneWidgets(); + void placeObject(const std::string sceneNode, const Ogre::Vector3 &pos); // FIXME + Ogre::Camera *getCamera(); // friend access Ogre::Viewport *getViewport(); // friend access }; diff --git a/apps/opencs/view/render/pathgridpoint.cpp b/apps/opencs/view/render/pathgridpoint.cpp new file mode 100644 index 000000000..2455ea1a8 --- /dev/null +++ b/apps/opencs/view/render/pathgridpoint.cpp @@ -0,0 +1,38 @@ +#include "pathgridpoint.hpp" + +#include // FIXME + +#include +#include + +//#include "../../model/world/pathgrid.hpp" +#include "../world/physicssystem.hpp" + +#include "elements.hpp" + +namespace CSVRender +{ + PathgridPoint::PathgridPoint(const std::string &name, + Ogre::SceneNode *cellNode, const Ogre::Vector3 &pos, CSVWorld::PhysicsSystem *physics) + : mBase(cellNode), mPhysics(physics) + { + mBase = cellNode->createChildSceneNode(); + + mPgPoint = NifOgre::Loader::createObjects(mBase, "pathgrid_pt.nif"); + mPgPoint->setVisibilityFlags(Element_Pathgrid); + mBase->setPosition(pos); + + physics->addObject("pathgrid_pt.nif", + mBase->getName(), name, 1, pos, Ogre::Quaternion::IDENTITY); + } + + PathgridPoint::~PathgridPoint() + { + mPgPoint.setNull(); + + mPhysics->removeObject(mBase->getName()); + + if (mBase) + mBase->getCreator()->destroySceneNode(mBase); + } +} diff --git a/apps/opencs/view/render/pathgridpoint.hpp b/apps/opencs/view/render/pathgridpoint.hpp new file mode 100644 index 000000000..f4fa42790 --- /dev/null +++ b/apps/opencs/view/render/pathgridpoint.hpp @@ -0,0 +1,35 @@ +#ifndef OPENCS_VIEW_PATHGRIDPOINT_H +#define OPENCS_VIEW_PATHGRIDPOINT_H + +#include + +namespace Ogre +{ + class Vector3; + class SceneNode; + class SceneManager; +} + +namespace CSVWorld +{ + class PhysicsSystem; +} + +namespace CSVRender +{ + class PathgridPoint + { + CSVWorld::PhysicsSystem *mPhysics; // local copy + NifOgre::ObjectScenePtr mPgPoint; + Ogre::SceneNode *mBase; + + public: + + PathgridPoint(const std::string &name, + Ogre::SceneNode *cellNode, const Ogre::Vector3 &pos, CSVWorld::PhysicsSystem *physics); + + ~PathgridPoint(); + }; +} + +#endif // OPENCS_VIEW_PATHGRIDPOINT_H diff --git a/files/materials/pathgrid_pt.nif b/files/materials/pathgrid_pt.nif new file mode 100644 index 0000000000000000000000000000000000000000..579b03036c05d5deefe564880dbe312ebbb671f2 GIT binary patch literal 1683 zcmb_c&2G~`5T2w?TS{o5<)=VFsl*Xg0afBs5fvdM4p>M~t z{C?Np?FC)$y&eQghexm&7u~;CWz{hD0=54Y+fzoWSk5~yRND7?35Oe)*tVL@XADTG z$msazOQxZWjz48KHiA7Fu|IzA>~=bx)-sfI<6e8A_tM49L(g%nZumu9vX(p%#5uQlEoZ8R#7-VYypD5R1EVB3_buexwNtrloXhIZ@iTF9 z{Hz^${+)}5IF;yZR(x^t`RvblZ2ox}hRvJ-a%RA%8BP(Knrxg>jG7~^xAk0x1sv9V z%5N;g-(S>Q9HU>GX1)qD24gYiESTIxGG_#)x;!zCin-NQ+-pZ_Xf6w~DA&+ju7hzy zmSsh5$}QZ@HMuQ!;I=7Xbdk!1z;9(1`L8re49DCSSsnHYnc}(&b DaQ$}< literal 0 HcmV?d00001 From 2c09b9ba212c699e53045e81d5d8ba936a67ce9a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 9 Nov 2014 12:34:26 +1100 Subject: [PATCH 002/441] Fix scene node being erased in the object reference map. --- apps/opencs/view/world/physicssystem.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index 57909f4d3..13b29ba5a 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -70,9 +70,6 @@ namespace CSVWorld if(referenceId != "") { - mSceneNodeToRefId.erase(sceneNodeName); - mSceneNodeToMesh.erase(sceneNodeName); - // find which SceneManager has this object Ogre::SceneManager *sceneManager = findSceneManager(sceneNodeName); if(!sceneManager) @@ -97,7 +94,8 @@ namespace CSVWorld mRefIdToSceneNode.begin(); for(; itRef != mRefIdToSceneNode.end(); ++itRef) { - if((*itRef).second.find(sceneManager) != (*itRef).second.end()) + if((*itRef).first == referenceId && + (*itRef).second.find(sceneManager) != (*itRef).second.end()) { (*itRef).second.erase(sceneManager); break; @@ -110,6 +108,9 @@ namespace CSVWorld mEngine->removeRigidBody(referenceId); mEngine->deleteRigidBody(referenceId); } + + mSceneNodeToRefId.erase(sceneNodeName); + mSceneNodeToMesh.erase(sceneNodeName); } } @@ -252,11 +253,13 @@ namespace CSVWorld return std::make_pair("", Ogre::Vector3(0,0,0)); else { + // FIXME: maybe below logic belongs in the caller, i.e. terrainUnderCursor or + // objectUnderCursor std::string name = refIdToSceneNode(result.first, sceneMgr); if(name == "") - name = result.first; + name = result.first; // prob terrain else - name = refIdToSceneNode(result.first, sceneMgr); + name = refIdToSceneNode(result.first, sceneMgr); // prob object return std::make_pair(name, ray.getPoint(farClipDist*result.second)); } From a5a76cadca8f5cc4002eec0f6d2cf2c0ffeb997a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 9 Nov 2014 14:36:58 +1100 Subject: [PATCH 003/441] Adding, deleting and moving pathgrid points work. However still not saving to the document. --- apps/opencs/view/render/cell.cpp | 204 +++++++++++++++++- apps/opencs/view/render/cell.hpp | 16 +- apps/opencs/view/render/mousestate.cpp | 38 +++- apps/opencs/view/render/mousestate.hpp | 3 +- .../view/render/pagedworldspacewidget.cpp | 94 ++++++++ .../view/render/pagedworldspacewidget.hpp | 13 ++ apps/opencs/view/render/pathgridpoint.cpp | 35 +++ apps/opencs/view/render/pathgridpoint.hpp | 4 + .../view/render/unpagedworldspacewidget.cpp | 18 ++ .../view/render/unpagedworldspacewidget.hpp | 6 + apps/opencs/view/render/worldspacewidget.cpp | 50 +++++ apps/opencs/view/render/worldspacewidget.hpp | 10 + 12 files changed, 474 insertions(+), 17 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 6979a4042..e703c8a9d 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -112,7 +112,8 @@ bool CSVRender::Cell::addObjects (int start, int end) CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, const std::string& id, CSVWorld::PhysicsSystem *physics, const Ogre::Vector3& origin) -: mData (data), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager), mPhysics(physics) +: mData (data), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager), mPhysics(physics), + mPathgridId("") { mCellNode = sceneManager->getRootSceneNode()->createChildSceneNode(); mCellNode->setPosition (origin); @@ -146,7 +147,7 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, } } - addPathgrid(); + loadPathgrid(); } CSVRender::Cell::~Cell() @@ -156,7 +157,13 @@ CSVRender::Cell::~Cell() iter != mPgEdges.end(); ++iter) { if(mSceneMgr->hasManualObject((*iter).second)) + { + Ogre::ManualObject *manual = mSceneMgr->getManualObject((*iter).second); + Ogre::SceneNode *node = manual->getParentSceneNode(); mSceneMgr->destroyManualObject((*iter).second); + if(mSceneMgr->hasSceneNode(node->getName())) + mSceneMgr->destroySceneNode(node); + } } destroyGridMaterials(); Ogre::ResourceGroupManager::getSingleton().destroyResourceGroup(DEBUGGING_GROUP); @@ -306,7 +313,14 @@ float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const return -std::numeric_limits::max(); } -void CSVRender::Cell::addPathgrid() +// FIXME: +// - updating indicies +// - collision mask +// - add pathgrid point above an object +// - adding edges +// - save to document & signals +// - repainting edges while moving +void CSVRender::Cell::loadPathgrid() { if(!Ogre::ResourceGroupManager::getSingleton().resourceGroupExists(DEBUGGING_GROUP)) Ogre::ResourceGroupManager::getSingleton().createResourceGroup(DEBUGGING_GROUP); @@ -318,19 +332,20 @@ void CSVRender::Cell::addPathgrid() int index = pathgridCollection.searchId(mId); if(index != -1) { - const CSMWorld::Pathgrid pathgrid = pathgridCollection.getRecord(index).get(); + const CSMWorld::Pathgrid &pathgrid = pathgridCollection.getRecord(index).get(); + mPathgridId = pathgrid.mId; // FIXME: temporary storage (should be document) + mPoints.resize(pathgrid.mPoints.size()); std::vector::const_iterator iter = pathgrid.mPoints.begin(); for(index = 0; iter != pathgrid.mPoints.end(); ++iter, ++index) { - std::ostringstream stream; - stream << "Pathgrid_" << mId << "_" << index; - std::string name = stream.str(); + std::string name = PathgridPoint::getName(pathgrid.mId, index); Ogre::Vector3 pos = Ogre::Vector3(worldsize*mX+(*iter).mX, worldsize*mY+(*iter).mY, (*iter).mZ); mPgPoints.insert(std::make_pair(name, new PathgridPoint(name, mCellNode, pos, mPhysics))); + mPoints[index] = *iter; // FIXME: temporary storage (should be document) } for(ESM::Pathgrid::EdgeList::const_iterator it = pathgrid.mEdges.begin(); @@ -343,7 +358,7 @@ void CSVRender::Cell::addPathgrid() const ESM::Pathgrid::Point &p1 = pathgrid.mPoints[edge.mV1]; std::ostringstream stream; - stream << mId << "_" << edge.mV0 << " " << edge.mV1; + stream << pathgrid.mId << "_" << edge.mV0 << " " << edge.mV1; std::string name = stream.str(); Ogre::ManualObject *line = createPathgridEdge(name, @@ -353,6 +368,179 @@ void CSVRender::Cell::addPathgrid() node->attachObject(line); mPgEdges.insert(std::make_pair(std::make_pair(edge.mV0, edge.mV1), name)); + mEdges.push_back(*it); // FIXME: temporary storage (should be document) + } + } +} + +// NOTE: pos is in world coordinates +// FIXME: save to the document +void CSVRender::Cell::pathgridPointAdded(const Ogre::Vector3 &pos) +{ + std::string name = PathgridPoint::getName(mId, mPoints.size()); + + mPgPoints.insert(std::make_pair(name, new PathgridPoint(name, mCellNode, pos, mPhysics))); + + // store to document + ESM::Pathgrid::Point point((int)pos.x, (int)pos.y, (int)pos.z); + point.mConnectionNum = 0; + mPoints.push_back(point); + mPathgridId = mId; + // FIXME: update other scene managers +} + +// FIXME: save to the document +void CSVRender::Cell::pathgridPointRemoved(const std::string &name) +{ + std::pair result = PathgridPoint::getIdAndIndex(name); + if(result.first == "") + return; + + std::string pathgridId = result.first; + int index = result.second; + + // check if the point exists + if(index < 0 || mPathgridId != pathgridId || index >= mPoints.size()) + return; + + int numToDelete = mPoints[index].mConnectionNum * 2; // for sanity check later + int edgeCount = 0; + + // find edges to delete + std::vector > edges; + for(unsigned i = 0; i < mEdges.size(); ++i) + { + if(mEdges[i].mV0 == index || mEdges[i].mV1 == index) + { + for(std::map, std::string>::iterator iter = mPgEdges.begin(); + iter != mPgEdges.end(); ++iter) + { + if((*iter).first.first == index || (*iter).first.second == index) + { + edges.push_back(std::make_pair((*iter).first.first, (*iter).first.second)); + } + } } } + + // delete the edges + for(std::vector >::iterator iter = edges.begin(); + iter != edges.end(); ++iter) + { + std::string name = mPgEdges[*iter]; + if(mSceneMgr->hasManualObject(name)) + { + // remove manual objects + Ogre::ManualObject *manual = mSceneMgr->getManualObject(name); + Ogre::SceneNode *node = manual->getParentSceneNode(); + mSceneMgr->destroyManualObject(name); + if(mSceneMgr->hasSceneNode(node->getName())) + mSceneMgr->destroySceneNode(node); + + edgeCount++; // for sanity check later + + // update map + mPgEdges.erase(*iter); + + // update document + assert(mPoints[(*iter).first].mConnectionNum > 0); + mPoints[(*iter).first].mConnectionNum -= 1; + for(unsigned i = mEdges.size() - 1; i > 0; --i) + { + if(mEdges[i].mV0 == index || mEdges[i].mV1 == index) + mEdges.erase(mEdges.begin() + i); + } + } + } + + if(edgeCount != numToDelete) + { + // WARNING: continue anyway? Or should this be an exception? + std::cerr << "The no of edges del does not match the no of conn for: " + << mPathgridId + "_" + QString::number(index).toStdString() << std::endl; + } + + if(edgeCount || mPoints[index].mConnectionNum == 0) + { + // remove the point + delete mPgPoints[name]; + mPgPoints.erase(name); + // FIXME: update other scene managers + } + + // store to document + //mPoints.erase(mPoints.begin() + index); // WARNING: Can't erase because the index will change + // FIXME: it should be possible to refresh indicies but that means index values + // can't be stored in maps, names, etc +} + +// NOTE: newPos is in world coordinates +void CSVRender::Cell::pathgridPointMoved(const std::string &name, const Ogre::Vector3 &newPos) +{ + std::pair result = PathgridPoint::getIdAndIndex(name); + if(result.first == "") + return; + + std::string pathgridId = result.first; + int index = result.second; + + // check if the point exists + if(index < 0 || mPathgridId != pathgridId || index >= mPoints.size()) + return; + + float worldsize = ESM::Land::REAL_SIZE; + + // delete then recreate the edges + for(unsigned i = 0; i < mEdges.size(); ++i) + { + if(mEdges[i].mV0 == index || mEdges[i].mV1 == index) + { + std::ostringstream stream; + stream << pathgridId << "_" << mEdges[i].mV0 << " " << mEdges[i].mV1; + std::string name = stream.str(); + + if(mSceneMgr->hasManualObject(name)) + { + // remove manual objects + Ogre::ManualObject *manual = mSceneMgr->getManualObject(name); + Ogre::SceneNode *node = manual->getParentSceneNode(); + mSceneMgr->destroyManualObject(name); + + if(mEdges[i].mV0 == index) + { + const ESM::Pathgrid::Point &p1 = mPoints[mEdges[i].mV1]; + + Ogre::ManualObject *line = createPathgridEdge(name, + newPos, + Ogre::Vector3(worldsize*mX+p1.mX, worldsize*mY+p1.mY, p1.mZ)); + line->setVisibilityFlags(Element_Pathgrid); + node->attachObject(line); + } + else if(mEdges[i].mV1 == index) + { + const ESM::Pathgrid::Point &p0 = mPoints[mEdges[i].mV0]; + + Ogre::ManualObject *line = createPathgridEdge(name, + Ogre::Vector3(worldsize*mX+p0.mX, worldsize*mY+p0.mY, p0.mZ), + newPos); + line->setVisibilityFlags(Element_Pathgrid); + node->attachObject(line); + } + } + } + } +} + +// FIXME: save to the document +void CSVRender::Cell::addPathgridEdge() +{ + // check if the points exist + // update the edges + // store to document + // FIXME: update other scene managers +} + +// FIXME: save to the document +void CSVRender::Cell::removePathgridEdge() +{ } diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index c88b59353..5d7b9088a 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -8,6 +8,7 @@ #include #include +#include // FIXME: temporaty storage until saving to document #include "object.hpp" @@ -23,6 +24,7 @@ namespace Ogre namespace CSMWorld { class Data; + class Pathgrid; } namespace CSVWorld @@ -42,6 +44,11 @@ namespace CSVRender std::map mObjects; std::map mPgPoints; std::map, std::string> mPgEdges; + + ESM::Pathgrid::PointList mPoints; // FIXME: temporary storage until saving to document + ESM::Pathgrid::EdgeList mEdges; // FIXME: temporary storage until saving to document + std::string mPathgridId; // FIXME: temporary storage until saving to document + std::auto_ptr mTerrain; CSVWorld::PhysicsSystem *mPhysics; Ogre::SceneManager *mSceneMgr; @@ -88,14 +95,21 @@ namespace CSVRender float getTerrainHeightAt(const Ogre::Vector3 &pos) const; + void pathgridPointAdded(const Ogre::Vector3 &pos); + void pathgridPointMoved(const std::string &name, const Ogre::Vector3 &newPos); + void pathgridPointRemoved(const std::string &name); + private: // for drawing pathgrid points & lines void createGridMaterials(); void destroyGridMaterials(); - void addPathgrid(); + void loadPathgrid(); Ogre::ManualObject *createPathgridEdge(const std::string &name, const Ogre::Vector3 &start, const Ogre::Vector3 &end); + + void addPathgridEdge(); + void removePathgridEdge(); }; } diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index aa8f1a785..bceae716c 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -209,9 +209,9 @@ namespace CSVRender // print some debug info std::string referenceId = mPhysics->sceneNodeToRefId(result.first); if(referenceId != "") - std::cout << "result: " << referenceId << std::endl; + std::cout << "result grab release: " << referenceId << std::endl; else - std::cout << "result: " << result.first << std::endl; + std::cout << "result grab release: " << result.first << std::endl; std::cout << " hit pos "+ QString::number(result.second.x).toStdString() + ", " + QString::number(result.second.y).toStdString() + ", " + QString::number(result.second.z).toStdString() << std::endl; @@ -234,7 +234,20 @@ namespace CSVRender // move pathgrid point, but don't save yet (need pathgrid // table feature & its data structure to be completed) // FIXME: need to signal PathgridPoint object of change - placeObject(mGrabbedSceneNode, pos); + std::pair result = + terrainUnderCursor(event->x(), event->y()); // FIXME: some pathgrid points are on objects + if(result.first != "") + { + // FIXME: rather than just updating at the end, should + // consider providing visual feedback of terrain height + // while dragging the pathgrid point (maybe check whether + // the object is a pathgrid point at the begging and set + // a flag?) + // FIXME: this also disallows dragging over objects, so + // may need to use ignoreObjects while raycasting + placeObject(mGrabbedSceneNode, result.second); + mParent->pathgridMoved(referenceId, result.second); + } } else { @@ -389,6 +402,21 @@ namespace CSVRender } } + // FIXME: castRay converts referenceId to scene node name only to be re-converted + // here - investigate whether refactoring required + std::pair MouseState::pgPointUnderCursor(const int mouseX, const int mouseY) + { + std::pair result = objectUnderCursor(mouseX, mouseY); + std::string referenceId = mPhysics->sceneNodeToRefId(result.first); + if(result.first != "" && + referenceId != "" && QString(referenceId.c_str()).contains(QRegExp("^Pathgrid"))) + { + return std::make_pair(referenceId, result.second); + } + + return std::make_pair("", Ogre::Vector3()); + } + std::pair MouseState::mousePositionOnPlane(const QPoint &pos, const Ogre::Plane &plane) { // using a really small value seems to mess up with the projections @@ -417,10 +445,6 @@ namespace CSVRender std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); if(result.first != "") { - std::cout << "result: " << result.first << std::endl; - std::cout << " hit pos "+ QString::number(result.second.x).toStdString() - + ", " + QString::number(result.second.y).toStdString() - + ", " + QString::number(result.second.z).toStdString() << std::endl; // FIXME: is there a better way to distinguish terrain from objects? QString name = QString(result.first.c_str()); if(name.contains(QRegExp("^HeightField"))) diff --git a/apps/opencs/view/render/mousestate.hpp b/apps/opencs/view/render/mousestate.hpp index 36d04ef6b..b56860cac 100644 --- a/apps/opencs/view/render/mousestate.hpp +++ b/apps/opencs/view/render/mousestate.hpp @@ -71,13 +71,14 @@ namespace CSVRender void mouseReleaseEvent (QMouseEvent *event); void mouseDoubleClickEvent (QMouseEvent *event); bool wheelEvent (QWheelEvent *event); + std::pair pgPointUnderCursor(const int mouseX, const int mouseY); + std::pair terrainUnderCursor(const int mouseX, const int mouseY); void cancelDrag(); private: std::pair mousePositionOnPlane(const QPoint &pos, const Ogre::Plane &plane); - std::pair terrainUnderCursor(const int mouseX, const int mouseY); std::pair objectUnderCursor(const int mouseX, const int mouseY); std::pair planeAxis(); void updateSceneWidgets(); diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 49e7e1f09..e528733c0 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -22,6 +22,7 @@ #include "../widget/scenetooltoggle.hpp" +#include "pathgridpoint.hpp" #include "elements.hpp" bool CSVRender::PagedWorldspaceWidget::adjustCells() @@ -295,6 +296,99 @@ void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent flagAsModified(); } +//void CSVRender::PagedWorldspaceWidget::pathgridAdded (const QModelIndex& parent, +// int start, int end) +//{ +// // FIXME: +//} +// +//void CSVRender::PagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, +// const QModelIndex& bottomRight) +//{ +// // FIXME: +//} +// +//void CSVRender::PagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, +// int start, int end) +//{ +// // FIXME: +//} + +CSVRender::Cell *CSVRender::PagedWorldspaceWidget::findCell(const std::string &cellId) +{ + const CSMWorld::IdCollection& cells = mDocument.getData().getCells(); + + std::map::iterator iter (mCells.begin()); + for(; iter!= mCells.end(); ++iter) + { + int index = cells.searchId(cellId); + + if (index != -1 && cellId == iter->first.getId (mWorldspace)) + { + return iter->second; + } + } + + return NULL; +} + +// FIXME: pathgrid may be inserted above an object, the parsing needs to change +void CSVRender::PagedWorldspaceWidget::pathgridInserted (const std::string &terrain, const Ogre::Vector3 &pos) +{ + // decode name + QString id = QString(terrain.c_str()); + QRegExp terrainRe("^HeightField_([\\d-]+)_([\\d-]+)$"); + + if (id.isEmpty() || !id.startsWith("HeightField_")) + return; + + if (terrainRe.indexIn(id) == -1) + return; + + int cellX = terrainRe.cap(1).toInt(); + int cellY = terrainRe.cap(2).toInt(); + + std::ostringstream stream; + stream << "#" << cellX << " " << cellY; + + Cell *cell = findCell(stream.str()); + if(cell) + { + cell->pathgridPointAdded(pos); + flagAsModified(); + + return; + } +} + +void CSVRender::PagedWorldspaceWidget::pathgridMoved (const std::string &pgName, const Ogre::Vector3 &pos) +{ + std::pair result = PathgridPoint::getIdAndIndex(pgName); + + Cell *cell = findCell(result.first); + if(cell) + { + cell->pathgridPointMoved(pgName, pos); + flagAsModified(); + + return; + } +} + +void CSVRender::PagedWorldspaceWidget::pathgridAboutToBeRemoved (const std::string &pgName) +{ + std::pair result = PathgridPoint::getIdAndIndex(pgName); + + Cell *cell = findCell(result.first); + if(cell) + { + cell->pathgridPointRemoved(pgName); + flagAsModified(); + + return; + } +} + std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() { Ogre::Vector3 position = getCamera()->getPosition(); diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index fe79e761e..b5ca91880 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -49,8 +49,16 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end); + //virtual void pathgridAdded (const QModelIndex& parent, int start, int end); + + //virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + //virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end); + virtual std::string getStartupInstruction(); + Cell *findCell(const std::string &cellId); + public: PagedWorldspaceWidget (QWidget *parent, CSMDoc::Document& document); @@ -87,6 +95,11 @@ namespace CSVRender virtual void mouseDoubleClickEvent (QMouseEvent *event); + // FIXME: temporary only until signals from the document is implemented + virtual void pathgridInserted (const std::string &terrain, const Ogre::Vector3 &pos); + virtual void pathgridMoved (const std::string &pgName, const Ogre::Vector3 &pos); + virtual void pathgridAboutToBeRemoved (const std::string &pgName); + signals: void cellSelectionChanged (const CSMWorld::CellSelection& selection); diff --git a/apps/opencs/view/render/pathgridpoint.cpp b/apps/opencs/view/render/pathgridpoint.cpp index 2455ea1a8..3e365932f 100644 --- a/apps/opencs/view/render/pathgridpoint.cpp +++ b/apps/opencs/view/render/pathgridpoint.cpp @@ -2,6 +2,8 @@ #include // FIXME +#include + #include #include @@ -35,4 +37,37 @@ namespace CSVRender if (mBase) mBase->getCreator()->destroySceneNode(mBase); } + + // FIXME: Is there a way to identify the pathgrid point other than via the index? + // ESM::Pathgrid::Edge itself uses the indicies so any change (add/delete) must be + // propagated everywhere. + std::pair PathgridPoint::getIdAndIndex(const std::string &name) + { + // decode name + QString id = QString(name.c_str()); + QRegExp pathgridRe("^Pathgrid_(.+)_(\\d+)$"); + + if (id.isEmpty() || !id.startsWith("Pathgrid_")) + return std::make_pair("", -1); + + std::string pathgridId = ""; + int index = -1; + if (pathgridRe.indexIn(id) != -1) + { + pathgridId = pathgridRe.cap(1).toStdString(); + index = pathgridRe.cap(2).toInt(); + + return std::make_pair(pathgridId, index); + } + + return std::make_pair("", -1); + } + + std::string PathgridPoint::getName(const std::string &pathgridId, const int index) + { + std::ostringstream stream; + stream << "Pathgrid_" << pathgridId << "_" << index; + + return stream.str(); + } } diff --git a/apps/opencs/view/render/pathgridpoint.hpp b/apps/opencs/view/render/pathgridpoint.hpp index f4fa42790..3419082c3 100644 --- a/apps/opencs/view/render/pathgridpoint.hpp +++ b/apps/opencs/view/render/pathgridpoint.hpp @@ -29,6 +29,10 @@ namespace CSVRender Ogre::SceneNode *cellNode, const Ogre::Vector3 &pos, CSVWorld::PhysicsSystem *physics); ~PathgridPoint(); + + static std::pair getIdAndIndex(const std::string &name); + + static std::string getName(const std::string &pathgridId, int index); }; } diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 8012b1b24..a1a8a8921 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -160,6 +160,24 @@ void CSVRender::UnpagedWorldspaceWidget::referenceAdded (const QModelIndex& pare flagAsModified(); } +//void CSVRender::UnpagedWorldspaceWidget::pathgridAdded (const QModelIndex& parent, +// int start, int end) +//{ +// // FIXME: +//} +// +//void CSVRender::UnpagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, +// const QModelIndex& bottomRight) +//{ +// // FIXME: +//} +// +//void CSVRender::UnpagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, +// int start, int end) +//{ +// // FIXME: +//} + std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() { Ogre::Vector3 position = getCamera()->getPosition(); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 5924abaa9..ee397d467 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -62,6 +62,12 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end); + //virtual void pathgridAdded (const QModelIndex& index, int start, int end); + + //virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + //virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end); + virtual std::string getStartupInstruction(); private slots: diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 9c8676064..1fedc8aa1 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "../../model/world/universalid.hpp" #include "../../model/world/idtable.hpp" @@ -54,6 +55,16 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); + //QAbstractItemModel *pathgrids = + //document.getData().getTableModel (CSMWorld::UniversalId::Type_Pathgrid); + + //connect (pathgrids, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + //this, SLOT (pathgridAdded (const QModelIndex&, int, int))); + //connect (pathgrids, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + //this, SLOT (pathgridDataChanged (const QModelIndex&, const QModelIndex&))); + //connect (pathgrids, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + //this, SLOT (pathgridAboutToBeRemoved (const QModelIndex&, int, int))); + // associate WorldSpaceWidgets (and their SceneManagers) with Documents // then create physics if there is a new document mPhysics = CSVWorld::PhysicsManager::instance()->addSceneWidget(document, this); @@ -389,12 +400,51 @@ void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) SceneWidget::wheelEvent(event); } +// FIXME: mouse button events are processed in MouseState but key events are +// processed here - seems inconsistent void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) { if(event->key() == Qt::Key_Escape) { mMouse->cancelDrag(); } + else if(event->key() == Qt::Key_Delete) + { + QPoint p = this->mapFromGlobal(QCursor::pos()); + std::pair result = mMouse->pgPointUnderCursor(p.x(), p.y()); + if(result.first != "") + { + pathgridAboutToBeRemoved(result.first); + } + else + SceneWidget::keyPressEvent(event); + } + else if(event->key() == Qt::Key_Insert) + { + QPoint p = this->mapFromGlobal(QCursor::pos()); + std::pair result = mMouse->terrainUnderCursor(p.x(), p.y()); + if(result.first != "") + { + pathgridInserted(result.first, result.second); + } + else + SceneWidget::keyPressEvent(event); + } else SceneWidget::keyPressEvent(event); } + +// FIXME: temporary until signals from the document are implemented +void CSVRender::WorldspaceWidget::pathgridAboutToBeRemoved (const std::string &pgName) +{ +} + +// FIXME: temporary until signals from the document are implemented +void CSVRender::WorldspaceWidget::pathgridMoved (const std::string &pgName, const Ogre::Vector3 &newPos) +{ +} + +// FIXME: temporary until signals from the document are implemented +void CSVRender::WorldspaceWidget::pathgridInserted (const std::string &name, const Ogre::Vector3 &pos) +{ +} diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 5bad3933d..75dc0c264 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -109,6 +109,11 @@ namespace CSVRender virtual void wheelEvent (QWheelEvent *event); virtual void keyPressEvent (QKeyEvent *event); + // FIXME: temporary only until the signals from the document are implemented + virtual void pathgridInserted (const std::string &terrain, const Ogre::Vector3 &pos); + virtual void pathgridMoved (const std::string &pgName, const Ogre::Vector3 &pos); + virtual void pathgridAboutToBeRemoved (const std::string &pgName); + private: void dragEnterEvent(QDragEnterEvent *event); @@ -143,6 +148,11 @@ namespace CSVRender void debugProfileAboutToBeRemoved (const QModelIndex& parent, int start, int end); + //virtual void pathgridAdded (const QModelIndex& index, int start, int end) = 0; + + //virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; + + //virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; protected slots: From 895739d6bbc092076ac6ec6d0d9a3645bd2dc1a6 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 9 Nov 2014 17:53:54 +1100 Subject: [PATCH 004/441] Visibility mask working correctly. --- apps/opencs/view/render/cell.cpp | 1 - apps/opencs/view/render/mousestate.cpp | 8 +++++++- apps/opencs/view/world/physicssystem.cpp | 23 +++++++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index e703c8a9d..a53a2939f 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -315,7 +315,6 @@ float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const // FIXME: // - updating indicies -// - collision mask // - add pathgrid point above an object // - adding edges // - save to document & signals diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index bceae716c..ee7776890 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -285,6 +285,10 @@ namespace CSVRender if(result.first != "") { // FIXME: terrain editing goes here + std::cout << "result default/edit release: " << result.first << std::endl; + std::cout << " hit pos "+ QString::number(result.second.x).toStdString() + + ", " + QString::number(result.second.y).toStdString() + + ", " + QString::number(result.second.z).toStdString() << std::endl; } break; } @@ -456,6 +460,7 @@ namespace CSVRender return std::make_pair("", Ogre::Vector3()); } + // NOTE: also returns pathgrids std::pair MouseState::objectUnderCursor(const int mouseX, const int mouseY) { if(!getViewport()) @@ -473,8 +478,9 @@ namespace CSVRender { uint32_t visibilityMask = getViewport()->getVisibilityMask(); bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); + bool ignorePathgrid = !(visibilityMask & (uint32_t)CSVRender::Element_Pathgrid); - if(!ignoreObjects && mSceneManager->hasSceneNode(result.first)) + if((!ignoreObjects || !ignorePathgrid) && mSceneManager->hasSceneNode(result.first)) { return result; } diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index 13b29ba5a..788e42c55 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -243,14 +243,29 @@ namespace CSVWorld uint32_t visibilityMask = camera->getViewport()->getVisibilityMask(); bool ignoreHeightMap = !(visibilityMask & (uint32_t)CSVRender::Element_Terrain); bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); + bool ignorePathgrid = !(visibilityMask & (uint32_t)CSVRender::Element_Pathgrid); - Ogre::Vector3 norm; // not used - std::pair result = - mEngine->rayTest(_from, _to, !ignoreObjects, ignoreHeightMap, &norm); + std::pair result = std::make_pair("", -1); + short mask = OEngine::Physic::CollisionType_Raycasting; + std::vector > objects = mEngine->rayTest2(_from, _to, mask); + + for (std::vector >::iterator it = objects.begin(); + it != objects.end(); ++it) + { + if(ignorePathgrid && QString((*it).second.c_str()).contains(QRegExp("^Pathgrid"))) + continue; + else if(ignoreObjects && QString((*it).second.c_str()).contains(QRegExp("^ref#"))) + continue; + else if(ignoreHeightMap && QString((*it).second.c_str()).contains(QRegExp("^Height"))) + continue; + + result = std::make_pair((*it).second, (*it).first); + break; + } // result.first is the object's referenceId if(result.first == "") - return std::make_pair("", Ogre::Vector3(0,0,0)); + return std::make_pair("", Ogre::Vector3()); else { // FIXME: maybe below logic belongs in the caller, i.e. terrainUnderCursor or From f5ba74cd1015bd652301fb48bd7ee1f83044aeae Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 9 Nov 2014 18:10:44 +1100 Subject: [PATCH 005/441] Suppress warnings. --- apps/opencs/view/render/cell.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index a53a2939f..238f60bb5 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -399,7 +399,7 @@ void CSVRender::Cell::pathgridPointRemoved(const std::string &name) int index = result.second; // check if the point exists - if(index < 0 || mPathgridId != pathgridId || index >= mPoints.size()) + if(index < 0 || mPathgridId != pathgridId || (unsigned int)index >= mPoints.size()) return; int numToDelete = mPoints[index].mConnectionNum * 2; // for sanity check later @@ -484,7 +484,7 @@ void CSVRender::Cell::pathgridPointMoved(const std::string &name, const Ogre::Ve int index = result.second; // check if the point exists - if(index < 0 || mPathgridId != pathgridId || index >= mPoints.size()) + if(index < 0 || mPathgridId != pathgridId || (unsigned int)index >= mPoints.size()) return; float worldsize = ESM::Land::REAL_SIZE; From 9f16b86bd8fd8f01d23589f4bc201a3ee1fcee49 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 9 Nov 2014 18:21:47 +1100 Subject: [PATCH 006/441] Fix installing pathgrid_pt.nif --- files/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index 9b2325744..6ee219a8a 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -17,6 +17,7 @@ set(MATERIAL_FILES objects.shader objects.shaderset openmw.configuration + pathgrid_pt.nif quad.mat quad.shader quad.shaderset From 89eb30054be61f4c7eab911f16e48aef5ca7b03a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 9 Nov 2014 18:37:04 +1100 Subject: [PATCH 007/441] Fix deleting physic objects. --- apps/opencs/view/world/physicssystem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index 788e42c55..cae5563e6 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -103,7 +103,8 @@ namespace CSVWorld } // check whether the physics model should be deleted - if(mRefIdToSceneNode.find(referenceId) == mRefIdToSceneNode.end()) + itRef = mRefIdToSceneNode.find(referenceId); + if(itRef == mRefIdToSceneNode.end() || (*itRef).second.empty()) { mEngine->removeRigidBody(referenceId); mEngine->deleteRigidBody(referenceId); From 1f73fae6efd077a6f744b8e2179017ac6c8595b2 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 9 Nov 2014 20:03:29 +1100 Subject: [PATCH 008/441] Allow adding or dragging pathgrid points over objects as well as terrain. --- apps/opencs/view/render/cell.cpp | 3 +- apps/opencs/view/render/mousestate.cpp | 38 +++++++++------- apps/opencs/view/render/mousestate.hpp | 3 +- .../view/render/pagedworldspacewidget.cpp | 45 +++++++++++++------ apps/opencs/view/render/worldspacewidget.cpp | 2 +- 5 files changed, 57 insertions(+), 34 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 238f60bb5..4e3416111 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -315,8 +315,7 @@ float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const // FIXME: // - updating indicies -// - add pathgrid point above an object -// - adding edges +// - adding edges (need the ability to select a pathgrid and highlight) // - save to document & signals // - repainting edges while moving void CSVRender::Cell::loadPathgrid() diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index ee7776890..ee59770e4 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -234,8 +234,9 @@ namespace CSVRender // move pathgrid point, but don't save yet (need pathgrid // table feature & its data structure to be completed) // FIXME: need to signal PathgridPoint object of change + // FIXME: shouldn't allow pathgrid points under the cursor std::pair result = - terrainUnderCursor(event->x(), event->y()); // FIXME: some pathgrid points are on objects + anyUnderCursor(event->x(), event->y()); if(result.first != "") { // FIXME: rather than just updating at the end, should @@ -243,8 +244,6 @@ namespace CSVRender // while dragging the pathgrid point (maybe check whether // the object is a pathgrid point at the begging and set // a flag?) - // FIXME: this also disallows dragging over objects, so - // may need to use ignoreObjects while raycasting placeObject(mGrabbedSceneNode, result.second); mParent->pathgridMoved(referenceId, result.second); } @@ -440,13 +439,7 @@ namespace CSVRender std::pair MouseState::terrainUnderCursor(const int mouseX, const int mouseY) { - if(!getViewport()) - return std::make_pair("", Ogre::Vector3()); - - float x = (float) mouseX / getViewport()->getActualWidth(); - float y = (float) mouseY / getViewport()->getActualHeight(); - - std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); + std::pair result = anyUnderCursor(mouseX, mouseY); if(result.first != "") { // FIXME: is there a better way to distinguish terrain from objects? @@ -463,13 +456,7 @@ namespace CSVRender // NOTE: also returns pathgrids std::pair MouseState::objectUnderCursor(const int mouseX, const int mouseY) { - if(!getViewport()) - return std::make_pair("", Ogre::Vector3()); - - float x = (float) mouseX / getViewport()->getActualWidth(); - float y = (float) mouseY / getViewport()->getActualHeight(); - - std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); + std::pair result = anyUnderCursor(mouseX, mouseY); if(result.first != "") { // NOTE: anything not terrain is assumed to be an object, e.g pathgrid points @@ -490,6 +477,23 @@ namespace CSVRender return std::make_pair("", Ogre::Vector3()); } + std::pair MouseState::anyUnderCursor(const int mouseX, const int mouseY) + { + if(!getViewport()) + return std::make_pair("", Ogre::Vector3()); + + float x = (float) mouseX / getViewport()->getActualWidth(); + float y = (float) mouseY / getViewport()->getActualHeight(); + + std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); + if(result.first != "") + { + return result; + } + + return std::make_pair("", Ogre::Vector3()); + } + void MouseState::updateSceneWidgets() { std::map sceneWidgets = mPhysics->sceneWidgets(); diff --git a/apps/opencs/view/render/mousestate.hpp b/apps/opencs/view/render/mousestate.hpp index b56860cac..fd752349a 100644 --- a/apps/opencs/view/render/mousestate.hpp +++ b/apps/opencs/view/render/mousestate.hpp @@ -72,13 +72,14 @@ namespace CSVRender void mouseDoubleClickEvent (QMouseEvent *event); bool wheelEvent (QWheelEvent *event); std::pair pgPointUnderCursor(const int mouseX, const int mouseY); - std::pair terrainUnderCursor(const int mouseX, const int mouseY); + std::pair anyUnderCursor(const int mouseX, const int mouseY); void cancelDrag(); private: std::pair mousePositionOnPlane(const QPoint &pos, const Ogre::Plane &plane); + std::pair terrainUnderCursor(const int mouseX, const int mouseY); std::pair objectUnderCursor(const int mouseX, const int mouseY); std::pair planeAxis(); void updateSceneWidgets(); diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index e528733c0..506fd54df 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -21,6 +21,7 @@ #include "../../model/world/idtable.hpp" #include "../widget/scenetooltoggle.hpp" +#include "../world/physicssystem.hpp" #include "pathgridpoint.hpp" #include "elements.hpp" @@ -332,26 +333,44 @@ CSVRender::Cell *CSVRender::PagedWorldspaceWidget::findCell(const std::string &c return NULL; } -// FIXME: pathgrid may be inserted above an object, the parsing needs to change -void CSVRender::PagedWorldspaceWidget::pathgridInserted (const std::string &terrain, const Ogre::Vector3 &pos) +// NOTE: allow placing pathgrid points above objects and terrain +void CSVRender::PagedWorldspaceWidget::pathgridInserted (const std::string &name, const Ogre::Vector3 &pos) { - // decode name - QString id = QString(terrain.c_str()); - QRegExp terrainRe("^HeightField_([\\d-]+)_([\\d-]+)$"); + QString id = QString(name.c_str()); + std::string referenceId = getPhysics()->sceneNodeToRefId(name); // FIXME: move back - if (id.isEmpty() || !id.startsWith("HeightField_")) + bool terrain = id.startsWith("HeightField_"); + bool object = QString(referenceId.c_str()).startsWith("ref#"); + // don't allow placing another one on top of a pathgrid point + if (id.isEmpty() || (!terrain && !object)) return; - if (terrainRe.indexIn(id) == -1) - return; + std::string cellId; + if(terrain) + { + QRegExp nameRe("^HeightField_([\\d-]+)_([\\d-]+)$"); + if (nameRe.indexIn(id) == -1) + return; - int cellX = terrainRe.cap(1).toInt(); - int cellY = terrainRe.cap(2).toInt(); + int cellX = nameRe.cap(1).toInt(); + int cellY = nameRe.cap(2).toInt(); - std::ostringstream stream; - stream << "#" << cellX << " " << cellY; + std::ostringstream stream; + stream << "#" << cellX << " " << cellY; + cellId = stream.str(); + } + else + { + const CSMWorld::RefCollection& references = mDocument.getData().getReferences(); + int index = references.searchId(referenceId); + if(index == -1) + return; + + cellId = references.getData(index, references.findColumnIndex(CSMWorld::Columns::ColumnId_Cell)) + .toString().toUtf8().constData(); + } - Cell *cell = findCell(stream.str()); + Cell *cell = findCell(cellId); if(cell) { cell->pathgridPointAdded(pos); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 1fedc8aa1..4f85f3a30 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -422,7 +422,7 @@ void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) else if(event->key() == Qt::Key_Insert) { QPoint p = this->mapFromGlobal(QCursor::pos()); - std::pair result = mMouse->terrainUnderCursor(p.x(), p.y()); + std::pair result = mMouse->anyUnderCursor(p.x(), p.y()); if(result.first != "") { pathgridInserted(result.first, result.second); From d7804974e8e2921cbda8f048ae01f2ca730fe414 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 10 Nov 2014 20:37:07 +1100 Subject: [PATCH 009/441] Better mouse cursor tracking for the third axis. --- apps/opencs/view/render/mousestate.cpp | 127 ++++++++++++++++--------- apps/opencs/view/render/mousestate.hpp | 7 +- 2 files changed, 83 insertions(+), 51 deletions(-) diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index ee59770e4..989dbdb91 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -57,9 +57,9 @@ namespace CSVRender MouseState::MouseState(WorldspaceWidget *parent) : mParent(parent), mPhysics(parent->getPhysics()), mSceneManager(parent->getSceneManager()) - , mCurrentObj(""), mMouseState(Mouse_Default), mOldPos(0,0), mMouseEventTimer(0), mPlane(0) + , mCurrentObj(""), mMouseState(Mouse_Default), mOldCursorPos(0,0), mMouseEventTimer(0) , mGrabbedSceneNode(""), mOrigObjPos(Ogre::Vector3()), mOrigMousePos(Ogre::Vector3()) - , mCurrentMousePos(Ogre::Vector3()), mOffset(0.0f) + , mOldMousePos(Ogre::Vector3()), mPlane(0) , mColIndexPosX(0), mColIndexPosY(0), mColIndexPosZ(0), mIdTableModel(0) { const CSMWorld::RefCollection& references = mParent->mDocument.getData().getReferences(); @@ -79,11 +79,11 @@ namespace CSVRender Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().createPlane("mouse", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, *mPlane, - 300000,300000, // FIXME: use far clip dist? - 1,1, // segments - true, // normals - 1, // numTexCoordSets - 1,1, // uTile, vTile + 300000,300000, // FIXME: use far clip dist? + 1,1, // segments + true, // normals + 1, // numTexCoordSets + 1,1, // uTile, vTile planeRes.second // upVector ); } @@ -111,23 +111,24 @@ namespace CSVRender } case Mouse_Drag: { - if(event->pos() != mOldPos) // TODO: maybe don't update less than a quantum? + if(event->pos() != mOldCursorPos) // TODO: maybe don't update less than a quantum? { - mOldPos = event->pos(); + mOldCursorPos = event->pos(); // ray test against the plane to provide feedback to the user the - // relative movement of the object on the x-y plane - std::pair planeResult = mousePositionOnPlane(event->pos(), *mPlane); + // relative movement of the object on the movement plane + std::pair planeResult = mousePosOnPlane(event->pos(), *mPlane); if(planeResult.first) { if(mGrabbedSceneNode != "") { - std::pair planeRes = planeAxis(); - Ogre::Vector3 pos = mOrigObjPos + planeRes.first*mOffset; - mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(pos+planeResult.second-mOrigMousePos); - mCurrentMousePos = planeResult.second; - mPhysics->moveSceneNodes(mGrabbedSceneNode, pos+planeResult.second-mOrigMousePos); + Ogre::Vector3 pos = mOrigObjPos + (planeResult.second-mOrigMousePos); + + mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(pos); + mPhysics->moveSceneNodes(mGrabbedSceneNode, pos); updateSceneWidgets(); + + mOldMousePos = planeResult.second; } } } @@ -165,12 +166,11 @@ namespace CSVRender // mouse in relation to the object position std::pair planeRes = planeAxis(); mPlane->redefine(planeRes.first, result.second); - std::pair planeResult = mousePositionOnPlane(event->pos(), *mPlane); + std::pair planeResult = mousePosOnPlane(event->pos(), *mPlane); if(planeResult.first) { mOrigMousePos = planeResult.second; - mCurrentMousePos = planeResult.second; - mOffset = 0.0f; + mOldMousePos = planeResult.second; } mOrigObjPos = mSceneManager->getSceneNode(mGrabbedSceneNode)->getPosition(); @@ -206,6 +206,7 @@ namespace CSVRender mCurrentObj = result.first; } +//#if 0 // print some debug info std::string referenceId = mPhysics->sceneNodeToRefId(result.first); if(referenceId != "") @@ -215,17 +216,18 @@ namespace CSVRender std::cout << " hit pos "+ QString::number(result.second.x).toStdString() + ", " + QString::number(result.second.y).toStdString() + ", " + QString::number(result.second.z).toStdString() << std::endl; +//#endif } break; } case Mouse_Drag: { // final placement - std::pair planeResult = mousePositionOnPlane(event->pos(), *mPlane); + std::pair planeResult = mousePosOnPlane(event->pos(), *mPlane); if(planeResult.first && mGrabbedSceneNode != "") { - std::pair planeRes = planeAxis(); - Ogre::Vector3 pos = mOrigObjPos+planeRes.first*mOffset+planeResult.second-mOrigMousePos; + Ogre::Vector3 pos = mOrigObjPos + (planeResult.second-mOrigMousePos); + // use the saved scene node name since the physics model has not moved yet std::string referenceId = mPhysics->sceneNodeToRefId(mGrabbedSceneNode); @@ -234,18 +236,19 @@ namespace CSVRender // move pathgrid point, but don't save yet (need pathgrid // table feature & its data structure to be completed) // FIXME: need to signal PathgridPoint object of change - // FIXME: shouldn't allow pathgrid points under the cursor - std::pair result = - anyUnderCursor(event->x(), event->y()); - if(result.first != "") + //std::pair result = + //anyUnderCursor(event->x(), event->y()); + //std::string refId = mPhysics->sceneNodeToRefId(result.first); + //if(result.first != "" && // don't allow pathgrid points under the cursor + //!QString(refId.c_str()).contains(QRegExp("^Pathgrid"))) { // FIXME: rather than just updating at the end, should // consider providing visual feedback of terrain height // while dragging the pathgrid point (maybe check whether // the object is a pathgrid point at the begging and set // a flag?) - placeObject(mGrabbedSceneNode, result.second); - mParent->pathgridMoved(referenceId, result.second); + placeObject(mGrabbedSceneNode, pos); // result.second + mParent->pathgridMoved(referenceId, pos); // result.second } } else @@ -267,12 +270,11 @@ namespace CSVRender mMouseState = Mouse_Edit; // reset states - mCurrentMousePos = Ogre::Vector3(); // mouse pos to use in wheel event - mOrigMousePos = Ogre::Vector3(); // starting pos of mouse in world space - mOrigObjPos = Ogre::Vector3(); // starting pos of object in world space - mGrabbedSceneNode = ""; // id of the object - mOffset = 0.0f; // used for z-axis movement - mOldPos = QPoint(0, 0); // to calculate relative movement of mouse + mGrabbedSceneNode = ""; // id of the object + mOrigMousePos = Ogre::Vector3(); // starting pos of mouse in world space + mOrigObjPos = Ogre::Vector3(); // starting pos of object in world space + mOldMousePos = Ogre::Vector3(); // mouse pos to use in wheel event + mOldCursorPos = QPoint(0, 0); // to calculate relative movement of mouse } break; } @@ -284,10 +286,12 @@ namespace CSVRender if(result.first != "") { // FIXME: terrain editing goes here +//#if 0 std::cout << "result default/edit release: " << result.first << std::endl; std::cout << " hit pos "+ QString::number(result.second.x).toStdString() + ", " + QString::number(result.second.y).toStdString() + ", " + QString::number(result.second.z).toStdString() << std::endl; +//#endif } break; } @@ -298,9 +302,9 @@ namespace CSVRender void MouseState::mouseDoubleClickEvent (QMouseEvent *event) { - //event->ignore(); - mPhysics->toggleDebugRendering(mSceneManager); - mParent->flagAsModified(); + event->ignore(); + //mPhysics->toggleDebugRendering(mSceneManager); + //mParent->flagAsModified(); } bool MouseState::wheelEvent (QWheelEvent *event) @@ -313,17 +317,47 @@ namespace CSVRender /* FALL_THROUGH */ case Mouse_Drag: { - // move the object along the z axis during Mouse_Drag or Mouse_Grab + // move the object along the axis normal to the plane during Mouse_Drag or Mouse_Grab if (event->delta()) { - // seems positive is up and negative is down - mOffset += (event->delta()/1); // FIXME: arbitrary number, make config option? + // The mouse point is where the mouse points the object when dragging starts. + // The object position is usually a little away from the mount point. + // Get the new world position of mouse on the plane offset from the wheel std::pair planeRes = planeAxis(); - Ogre::Vector3 pos = mOrigObjPos + planeRes.first*mOffset; - mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(pos+mCurrentMousePos-mOrigMousePos); - mPhysics->moveSceneNodes(mGrabbedSceneNode, pos+mCurrentMousePos-mOrigMousePos); + Ogre::Vector3 mousePos = mOldMousePos + planeRes.first*(event->delta()/2); + + // Move the movement plane to the new mouse position. The plane is created on + // the mouse point (i.e. not the object position) + mPlane->redefine(planeRes.first, mousePos); + + // Calculate the new screen position of the cursor + Ogre::Vector3 screenPos = + getCamera()->getProjectionMatrix() * getCamera()->getViewMatrix() * mousePos; + int screenX = (screenPos.x/2+0.5) * getViewport()->getActualWidth(); + int screenY = (1-(screenPos.y/2+0.5)) * getViewport()->getActualHeight(); + + // Move the cursor to the new screen position + QCursor::setPos(mParent->mapToGlobal(QPoint(screenX, screenY))); + + // Use the new position to check the world position of the mouse + std::pair planeResult = + mousePosOnPlane(QPoint(screenX, screenY), *mPlane); + { + if(planeResult.first) + mousePos = planeResult.second; + } + + // Find the final world position of the cursor + Ogre::Vector3 finalPos = mOrigObjPos + (mousePos-mOrigMousePos); + + mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(finalPos); + mPhysics->moveSceneNodes(mGrabbedSceneNode, finalPos); updateSceneWidgets(); + + // remember positions for next time + mOldMousePos = mousePos; + mOldCursorPos = QPoint(screenX, screenY); } break; } @@ -353,14 +387,13 @@ namespace CSVRender // reset states mMouseState = Mouse_Default; - mCurrentMousePos = Ogre::Vector3(); + mOldMousePos = Ogre::Vector3(); mOrigMousePos = Ogre::Vector3(); mOrigObjPos = Ogre::Vector3(); mGrabbedSceneNode = ""; mCurrentObj = ""; - mOldPos = QPoint(0, 0); + mOldCursorPos = QPoint(0, 0); mMouseEventTimer->invalidate(); - mOffset = 0.0f; break; } @@ -420,7 +453,7 @@ namespace CSVRender return std::make_pair("", Ogre::Vector3()); } - std::pair MouseState::mousePositionOnPlane(const QPoint &pos, const Ogre::Plane &plane) + std::pair MouseState::mousePosOnPlane(const QPoint &pos, const Ogre::Plane &plane) { // using a really small value seems to mess up with the projections float nearClipDistance = getCamera()->getNearClipDistance(); // save existing diff --git a/apps/opencs/view/render/mousestate.hpp b/apps/opencs/view/render/mousestate.hpp index fd752349a..d21735a78 100644 --- a/apps/opencs/view/render/mousestate.hpp +++ b/apps/opencs/view/render/mousestate.hpp @@ -46,15 +46,14 @@ namespace CSVRender CSVWorld::PhysicsSystem *mPhysics; // local copy Ogre::SceneManager *mSceneManager; // local copy - QPoint mOldPos; + QPoint mOldCursorPos; std::string mCurrentObj; std::string mGrabbedSceneNode; QElapsedTimer *mMouseEventTimer; Ogre::Plane *mPlane; Ogre::Vector3 mOrigObjPos; Ogre::Vector3 mOrigMousePos; - Ogre::Vector3 mCurrentMousePos; - float mOffset; + Ogre::Vector3 mOldMousePos; CSMWorld::IdTable *mIdTableModel; int mColIndexPosX; @@ -78,7 +77,7 @@ namespace CSVRender private: - std::pair mousePositionOnPlane(const QPoint &pos, const Ogre::Plane &plane); + std::pair mousePosOnPlane(const QPoint &pos, const Ogre::Plane &plane); std::pair terrainUnderCursor(const int mouseX, const int mouseY); std::pair objectUnderCursor(const int mouseX, const int mouseY); std::pair planeAxis(); From 2412d127b0b2694f136e0f468034ffaec117560f Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 10 Nov 2014 22:29:01 +1100 Subject: [PATCH 010/441] Fix saving pathgrid positions when adding or moving. --- apps/opencs/view/render/cell.cpp | 36 +++++++++++++++++++++++++------- apps/opencs/view/render/cell.hpp | 5 +++-- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 4e3416111..043515907 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -18,7 +18,6 @@ #include "terrainstorage.hpp" #include "pathgridpoint.hpp" -// FIXME: redraw edges when pathgrid points are moved, added and deleted namespace CSVRender { // PLEASE NOTE: pathgrid edge code copied and adapted from mwrender/debugging @@ -314,7 +313,7 @@ float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const } // FIXME: -// - updating indicies +// - updating indicies, including mData // - adding edges (need the ability to select a pathgrid and highlight) // - save to document & signals // - repainting edges while moving @@ -324,7 +323,7 @@ void CSVRender::Cell::loadPathgrid() Ogre::ResourceGroupManager::getSingleton().createResourceGroup(DEBUGGING_GROUP); createGridMaterials(); - float worldsize = ESM::Land::REAL_SIZE; + int worldsize = ESM::Land::REAL_SIZE; CSMWorld::SubCellCollection& pathgridCollection = mData.getPathgrids(); int index = pathgridCollection.searchId(mId); @@ -373,14 +372,24 @@ void CSVRender::Cell::loadPathgrid() // NOTE: pos is in world coordinates // FIXME: save to the document -void CSVRender::Cell::pathgridPointAdded(const Ogre::Vector3 &pos) +void CSVRender::Cell::pathgridPointAdded(const Ogre::Vector3 &pos, bool interior) { std::string name = PathgridPoint::getName(mId, mPoints.size()); mPgPoints.insert(std::make_pair(name, new PathgridPoint(name, mCellNode, pos, mPhysics))); // store to document - ESM::Pathgrid::Point point((int)pos.x, (int)pos.y, (int)pos.z); + int worldsize = ESM::Land::REAL_SIZE; + + int x = pos.x; + int y = pos.y; + if(!interior) + { + x = x - (worldsize * mX); + y = y - (worldsize * mY); + } + + ESM::Pathgrid::Point point(x, y, (int)pos.z); point.mConnectionNum = 0; mPoints.push_back(point); mPathgridId = mId; @@ -473,7 +482,8 @@ void CSVRender::Cell::pathgridPointRemoved(const std::string &name) } // NOTE: newPos is in world coordinates -void CSVRender::Cell::pathgridPointMoved(const std::string &name, const Ogre::Vector3 &newPos) +void CSVRender::Cell::pathgridPointMoved(const std::string &name, + const Ogre::Vector3 &newPos, bool interior) { std::pair result = PathgridPoint::getIdAndIndex(name); if(result.first == "") @@ -486,7 +496,19 @@ void CSVRender::Cell::pathgridPointMoved(const std::string &name, const Ogre::Ve if(index < 0 || mPathgridId != pathgridId || (unsigned int)index >= mPoints.size()) return; - float worldsize = ESM::Land::REAL_SIZE; + int worldsize = ESM::Land::REAL_SIZE; + + int x = newPos.x; + int y = newPos.y; + if(!interior) + { + x = x - (worldsize * mX); + y = y - (worldsize * mY); + } + + mPoints[index].mX = x; + mPoints[index].mY = y; + mPoints[index].mZ = newPos.z; // delete then recreate the edges for(unsigned i = 0; i < mEdges.size(); ++i) diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 5d7b9088a..8a7f01f20 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -95,8 +95,9 @@ namespace CSVRender float getTerrainHeightAt(const Ogre::Vector3 &pos) const; - void pathgridPointAdded(const Ogre::Vector3 &pos); - void pathgridPointMoved(const std::string &name, const Ogre::Vector3 &newPos); + void pathgridPointAdded(const Ogre::Vector3 &pos, bool interior = false); + void pathgridPointMoved(const std::string &name, + const Ogre::Vector3 &newPos, bool interior = false); void pathgridPointRemoved(const std::string &name); private: From 48ecea71036d92752f7a228a21e1c4b93f2d7394 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 10 Nov 2014 22:38:29 +1100 Subject: [PATCH 011/441] Simplistic drop-to-ground functionality for pathgrid points. --- apps/opencs/view/render/mousestate.cpp | 17 +++++++---- apps/opencs/view/world/physicssystem.cpp | 37 ++++++++++++++++++++++++ apps/opencs/view/world/physicssystem.hpp | 2 ++ 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index 989dbdb91..01f16c339 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -236,12 +236,17 @@ namespace CSVRender // move pathgrid point, but don't save yet (need pathgrid // table feature & its data structure to be completed) // FIXME: need to signal PathgridPoint object of change - //std::pair result = - //anyUnderCursor(event->x(), event->y()); - //std::string refId = mPhysics->sceneNodeToRefId(result.first); - //if(result.first != "" && // don't allow pathgrid points under the cursor - //!QString(refId.c_str()).contains(QRegExp("^Pathgrid"))) + std::pair result = + anyUnderCursor(event->x(), event->y()); + std::string refId = mPhysics->sceneNodeToRefId(result.first); + if(result.first != "" && // don't allow pathgrid points under the cursor + !QString(refId.c_str()).contains(QRegExp("^Pathgrid"))) { + // drop (does not work if placed below the object/terrain) + // maybe look for closest object/terrain in both directions? + std::pair res = mPhysics->distToGround(pos, getCamera()); + if(res.first != "") + pos.z -= res.second; // FIXME: rather than just updating at the end, should // consider providing visual feedback of terrain height // while dragging the pathgrid point (maybe check whether @@ -250,6 +255,8 @@ namespace CSVRender placeObject(mGrabbedSceneNode, pos); // result.second mParent->pathgridMoved(referenceId, pos); // result.second } + else + cancelDrag(); } else { diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index cae5563e6..55da2e221 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -281,6 +281,43 @@ namespace CSVWorld } } + std::pair PhysicsSystem::distToGround(Ogre::Vector3 &position, + Ogre::Camera *camera) + { + btVector3 _from, _to; + _from = btVector3(position.x, position.y, position.z); + _to = btVector3(position.x, position.y, position.z-300000); + + uint32_t visibilityMask = camera->getViewport()->getVisibilityMask(); + bool ignoreHeightMap = !(visibilityMask & (uint32_t)CSVRender::Element_Terrain); + bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); + bool ignorePathgrid = !(visibilityMask & (uint32_t)CSVRender::Element_Pathgrid); + + std::pair result = std::make_pair("", -1); + short mask = OEngine::Physic::CollisionType_Raycasting; + std::vector > objects = mEngine->rayTest2(_from, _to, mask); + + for (std::vector >::iterator it = objects.begin(); + it != objects.end(); ++it) + { + if(ignorePathgrid && QString((*it).second.c_str()).contains(QRegExp("^Pathgrid"))) + continue; + else if(ignoreObjects && QString((*it).second.c_str()).contains(QRegExp("^ref#"))) + continue; + else if(ignoreHeightMap && QString((*it).second.c_str()).contains(QRegExp("^Height"))) + continue; + + result = std::make_pair((*it).second, (*it).first); + break; + } + + // result.first is the object's referenceId + if(result.first == "") + return std::make_pair("", -1); + else + return std::make_pair(result.first, 300000*result.second); + } + std::string PhysicsSystem::refIdToSceneNode(std::string referenceId, Ogre::SceneManager *sceneMgr) { return mRefIdToSceneNode[referenceId][sceneMgr]; diff --git a/apps/opencs/view/world/physicssystem.hpp b/apps/opencs/view/world/physicssystem.hpp index 0036bf769..f28272532 100644 --- a/apps/opencs/view/world/physicssystem.hpp +++ b/apps/opencs/view/world/physicssystem.hpp @@ -73,6 +73,8 @@ namespace CSVWorld std::pair castRay(float mouseX, float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera); + std::pair distToGround(Ogre::Vector3 &position, Ogre::Camera *camera); + std::string sceneNodeToRefId(std::string sceneNodeName); // for multi-scene manager per physics engine From 1504119da776121b79927c6188872bad06036cc6 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 11 Nov 2014 06:07:52 +1100 Subject: [PATCH 012/441] Increase the sensitivity of the wheel movement. --- apps/opencs/view/render/mousestate.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index 01f16c339..ba668901f 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -233,9 +233,9 @@ namespace CSVRender if(QString(referenceId.c_str()).contains(QRegExp("^Pathgrid"))) { - // move pathgrid point, but don't save yet (need pathgrid + // FIXME: move pathgrid point, but don't save yet (need pathgrid // table feature & its data structure to be completed) - // FIXME: need to signal PathgridPoint object of change + // Also need to signal PathgridPoint object of change std::pair result = anyUnderCursor(event->x(), event->y()); std::string refId = mPhysics->sceneNodeToRefId(result.first); @@ -256,7 +256,7 @@ namespace CSVRender mParent->pathgridMoved(referenceId, pos); // result.second } else - cancelDrag(); + cancelDrag(); // FIXME: does not allow editing if terrain not visible } else { @@ -331,8 +331,9 @@ namespace CSVRender // The object position is usually a little away from the mount point. // Get the new world position of mouse on the plane offset from the wheel + // FIXME: make the sensitivity a user setting and/or allow modifiers std::pair planeRes = planeAxis(); - Ogre::Vector3 mousePos = mOldMousePos + planeRes.first*(event->delta()/2); + Ogre::Vector3 mousePos = mOldMousePos + planeRes.first*(event->delta()/1.5); // Move the movement plane to the new mouse position. The plane is created on // the mouse point (i.e. not the object position) From 407edc770c0005a9f179717fa9532d063b8ecbd8 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 11 Nov 2014 07:47:35 +1100 Subject: [PATCH 013/441] Added a simplistic snap to closest object or terrain. --- apps/opencs/view/render/mousestate.cpp | 25 +++++---- apps/opencs/view/world/physicssystem.cpp | 70 +++++++++++++++++++++++- apps/opencs/view/world/physicssystem.hpp | 6 +- 3 files changed, 89 insertions(+), 12 deletions(-) diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index ba668901f..8f254f08d 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -242,18 +242,23 @@ namespace CSVRender if(result.first != "" && // don't allow pathgrid points under the cursor !QString(refId.c_str()).contains(QRegExp("^Pathgrid"))) { - // drop (does not work if placed below the object/terrain) - // maybe look for closest object/terrain in both directions? - std::pair res = mPhysics->distToGround(pos, getCamera()); + // snap (defaults to 300 or less) + // FIXME: sticks to the underside of the object if snapping up + std::pair res = + mPhysics->distToClosest(pos, getCamera(), 500); if(res.first != "") + { pos.z -= res.second; - // FIXME: rather than just updating at the end, should - // consider providing visual feedback of terrain height - // while dragging the pathgrid point (maybe check whether - // the object is a pathgrid point at the begging and set - // a flag?) - placeObject(mGrabbedSceneNode, pos); // result.second - mParent->pathgridMoved(referenceId, pos); // result.second + // FIXME: rather than just updating at the end, should + // consider providing visual feedback of terrain height + // while dragging the pathgrid point (maybe check whether + // the object is a pathgrid point at the begging and set + // a flag?) + placeObject(mGrabbedSceneNode, pos); // result.second + mParent->pathgridMoved(referenceId, pos); // result.second + } + else + cancelDrag(); } else cancelDrag(); // FIXME: does not allow editing if terrain not visible diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index 55da2e221..358bb90ca 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -281,7 +281,7 @@ namespace CSVWorld } } - std::pair PhysicsSystem::distToGround(Ogre::Vector3 &position, + std::pair PhysicsSystem::distToGround(const Ogre::Vector3 &position, Ogre::Camera *camera) { btVector3 _from, _to; @@ -318,6 +318,74 @@ namespace CSVWorld return std::make_pair(result.first, 300000*result.second); } + // FIXME: remove code duplication + std::pair PhysicsSystem::distToClosest(const Ogre::Vector3 &position, + Ogre::Camera *camera, const float limit) + { + uint32_t visibilityMask = camera->getViewport()->getVisibilityMask(); + bool ignoreHeightMap = !(visibilityMask & (uint32_t)CSVRender::Element_Terrain); + bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); + bool ignorePathgrid = !(visibilityMask & (uint32_t)CSVRender::Element_Pathgrid); + + short mask = OEngine::Physic::CollisionType_Raycasting; + + btVector3 _from, _to; + _from = btVector3(position.x, position.y, position.z); + _to = btVector3(position.x, position.y, position.z-limit); + + std::pair resDown = std::make_pair("", -1); + std::vector > objectsDown = mEngine->rayTest2(_from, _to, mask); + + for (std::vector >::iterator it = objectsDown.begin(); + it != objectsDown.end(); ++it) + { + if(ignorePathgrid && QString((*it).second.c_str()).contains(QRegExp("^Pathgrid"))) + continue; + else if(ignoreObjects && QString((*it).second.c_str()).contains(QRegExp("^ref#"))) + continue; + else if(ignoreHeightMap && QString((*it).second.c_str()).contains(QRegExp("^Height"))) + continue; + + resDown = std::make_pair((*it).second, (*it).first); + break; + } + + _to = btVector3(position.x, position.y, position.z+limit); + std::pair resUp = std::make_pair("", -1); + std::vector > objectsUp = mEngine->rayTest2(_from, _to, mask); + + for (std::vector >::iterator it = objectsDown.begin(); + it != objectsDown.end(); ++it) + { + if(ignorePathgrid && QString((*it).second.c_str()).contains(QRegExp("^Pathgrid"))) + continue; + else if(ignoreObjects && QString((*it).second.c_str()).contains(QRegExp("^ref#"))) + continue; + else if(ignoreHeightMap && QString((*it).second.c_str()).contains(QRegExp("^Height"))) + continue; + + resUp = std::make_pair((*it).second, (*it).first); + break; + } + + if(resDown.first != "") + { + if(resUp.first != "") + { + if(fabs(resUp.second) < fabs(resDown.second)) + return std::make_pair(resUp.first, -limit*resUp.second); + else + return std::make_pair(resDown.first, limit*resDown.second); + } + else + return std::make_pair(resDown.first, limit*resDown.second); + } + else if(resUp.first != "") + return std::make_pair(resUp.first, -limit*resUp.second); + else + return std::make_pair("", -1); + } + std::string PhysicsSystem::refIdToSceneNode(std::string referenceId, Ogre::SceneManager *sceneMgr) { return mRefIdToSceneNode[referenceId][sceneMgr]; diff --git a/apps/opencs/view/world/physicssystem.hpp b/apps/opencs/view/world/physicssystem.hpp index f28272532..6436b8743 100644 --- a/apps/opencs/view/world/physicssystem.hpp +++ b/apps/opencs/view/world/physicssystem.hpp @@ -73,7 +73,11 @@ namespace CSVWorld std::pair castRay(float mouseX, float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera); - std::pair distToGround(Ogre::Vector3 &position, Ogre::Camera *camera); + std::pair distToGround(const Ogre::Vector3 &position, + Ogre::Camera *camera); + + std::pair distToClosest(const Ogre::Vector3 &position, + Ogre::Camera *camera, const float limit = 300.0f); std::string sceneNodeToRefId(std::string sceneNodeName); From 3eb556ff8ac7bf756ae5c2e2b5a6c4e5d2b6622a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 11 Nov 2014 19:40:04 +1100 Subject: [PATCH 014/441] Enhanced snap functionality for pathgrid points. --- apps/opencs/view/render/mousestate.cpp | 31 ++++------ apps/opencs/view/world/physicssystem.cpp | 73 ++++-------------------- apps/opencs/view/world/physicssystem.hpp | 4 +- 3 files changed, 25 insertions(+), 83 deletions(-) diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index 8f254f08d..c9fcdbc86 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -236,29 +236,22 @@ namespace CSVRender // FIXME: move pathgrid point, but don't save yet (need pathgrid // table feature & its data structure to be completed) // Also need to signal PathgridPoint object of change - std::pair result = - anyUnderCursor(event->x(), event->y()); + std::pair result = + mPhysics->distToClosest(pos, getCamera(), 600); // snap std::string refId = mPhysics->sceneNodeToRefId(result.first); + if(result.first != "" && // don't allow pathgrid points under the cursor !QString(refId.c_str()).contains(QRegExp("^Pathgrid"))) { - // snap (defaults to 300 or less) - // FIXME: sticks to the underside of the object if snapping up - std::pair res = - mPhysics->distToClosest(pos, getCamera(), 500); - if(res.first != "") - { - pos.z -= res.second; - // FIXME: rather than just updating at the end, should - // consider providing visual feedback of terrain height - // while dragging the pathgrid point (maybe check whether - // the object is a pathgrid point at the begging and set - // a flag?) - placeObject(mGrabbedSceneNode, pos); // result.second - mParent->pathgridMoved(referenceId, pos); // result.second - } - else - cancelDrag(); + pos.z -= result.second; + pos.z += 2; // arbitrary number, lift up slightly (maybe change the nif?) + // FIXME: rather than just updating at the end, should + // consider providing visual feedback of terrain height + // while dragging the pathgrid point (maybe check whether + // the object is a pathgrid point at the begging and set + // a flag?) + placeObject(mGrabbedSceneNode, pos); // result.second + mParent->pathgridMoved(referenceId, pos); // result.second } else cancelDrag(); // FIXME: does not allow editing if terrain not visible diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index 358bb90ca..9ff945829 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -282,16 +282,17 @@ namespace CSVWorld } std::pair PhysicsSystem::distToGround(const Ogre::Vector3 &position, - Ogre::Camera *camera) + Ogre::Camera *camera, const float limit, bool ignorePgPoint) { btVector3 _from, _to; _from = btVector3(position.x, position.y, position.z); - _to = btVector3(position.x, position.y, position.z-300000); + _to = btVector3(position.x, position.y, position.z-limit); uint32_t visibilityMask = camera->getViewport()->getVisibilityMask(); bool ignoreHeightMap = !(visibilityMask & (uint32_t)CSVRender::Element_Terrain); bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); - bool ignorePathgrid = !(visibilityMask & (uint32_t)CSVRender::Element_Pathgrid); + bool ignorePathgrid = ignorePgPoint || + !(visibilityMask & (uint32_t)CSVRender::Element_Pathgrid); std::pair result = std::make_pair("", -1); short mask = OEngine::Physic::CollisionType_Raycasting; @@ -315,73 +316,21 @@ namespace CSVWorld if(result.first == "") return std::make_pair("", -1); else - return std::make_pair(result.first, 300000*result.second); + return std::make_pair(result.first, limit*result.second); } - // FIXME: remove code duplication + // tries to find the distance to the "top" of the closest object std::pair PhysicsSystem::distToClosest(const Ogre::Vector3 &position, Ogre::Camera *camera, const float limit) { - uint32_t visibilityMask = camera->getViewport()->getVisibilityMask(); - bool ignoreHeightMap = !(visibilityMask & (uint32_t)CSVRender::Element_Terrain); - bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); - bool ignorePathgrid = !(visibilityMask & (uint32_t)CSVRender::Element_Pathgrid); - - short mask = OEngine::Physic::CollisionType_Raycasting; - - btVector3 _from, _to; - _from = btVector3(position.x, position.y, position.z); - _to = btVector3(position.x, position.y, position.z-limit); - - std::pair resDown = std::make_pair("", -1); - std::vector > objectsDown = mEngine->rayTest2(_from, _to, mask); - - for (std::vector >::iterator it = objectsDown.begin(); - it != objectsDown.end(); ++it) - { - if(ignorePathgrid && QString((*it).second.c_str()).contains(QRegExp("^Pathgrid"))) - continue; - else if(ignoreObjects && QString((*it).second.c_str()).contains(QRegExp("^ref#"))) - continue; - else if(ignoreHeightMap && QString((*it).second.c_str()).contains(QRegExp("^Height"))) - continue; - - resDown = std::make_pair((*it).second, (*it).first); - break; - } - - _to = btVector3(position.x, position.y, position.z+limit); - std::pair resUp = std::make_pair("", -1); - std::vector > objectsUp = mEngine->rayTest2(_from, _to, mask); - - for (std::vector >::iterator it = objectsDown.begin(); - it != objectsDown.end(); ++it) - { - if(ignorePathgrid && QString((*it).second.c_str()).contains(QRegExp("^Pathgrid"))) - continue; - else if(ignoreObjects && QString((*it).second.c_str()).contains(QRegExp("^ref#"))) - continue; - else if(ignoreHeightMap && QString((*it).second.c_str()).contains(QRegExp("^Height"))) - continue; + const float thickness = 50; // arbitrary number - resUp = std::make_pair((*it).second, (*it).first); - break; - } + std::pair resDown = + distToGround(Ogre::Vector3(position.x, position.y, position.z+thickness), + camera, limit+thickness, true); if(resDown.first != "") - { - if(resUp.first != "") - { - if(fabs(resUp.second) < fabs(resDown.second)) - return std::make_pair(resUp.first, -limit*resUp.second); - else - return std::make_pair(resDown.first, limit*resDown.second); - } - else - return std::make_pair(resDown.first, limit*resDown.second); - } - else if(resUp.first != "") - return std::make_pair(resUp.first, -limit*resUp.second); + return std::make_pair(resDown.first, resDown.second-thickness); else return std::make_pair("", -1); } diff --git a/apps/opencs/view/world/physicssystem.hpp b/apps/opencs/view/world/physicssystem.hpp index 6436b8743..589dca9c5 100644 --- a/apps/opencs/view/world/physicssystem.hpp +++ b/apps/opencs/view/world/physicssystem.hpp @@ -74,10 +74,10 @@ namespace CSVWorld float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera); std::pair distToGround(const Ogre::Vector3 &position, - Ogre::Camera *camera); + Ogre::Camera *camera, const float limit = 300000, bool ignorePgPoint = false); std::pair distToClosest(const Ogre::Vector3 &position, - Ogre::Camera *camera, const float limit = 300.0f); + Ogre::Camera *camera, const float limit = 100.0f); std::string sceneNodeToRefId(std::string sceneNodeName); From d65adc4376228d842c792c690bfb2bf95dbb3220 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 12 Nov 2014 22:04:48 +1100 Subject: [PATCH 015/441] Reduce code duplication. --- apps/opencs/view/render/mousestate.cpp | 144 ++++++------------ apps/opencs/view/render/mousestate.hpp | 10 +- .../view/render/pagedworldspacewidget.cpp | 5 +- .../view/render/pagedworldspacewidget.hpp | 2 +- apps/opencs/view/render/worldspacewidget.cpp | 8 +- apps/opencs/view/world/physicssystem.cpp | 46 +++--- apps/opencs/view/world/physicssystem.hpp | 9 +- 7 files changed, 83 insertions(+), 141 deletions(-) diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index c9fcdbc86..65596b752 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -58,8 +58,8 @@ namespace CSVRender MouseState::MouseState(WorldspaceWidget *parent) : mParent(parent), mPhysics(parent->getPhysics()), mSceneManager(parent->getSceneManager()) , mCurrentObj(""), mMouseState(Mouse_Default), mOldCursorPos(0,0), mMouseEventTimer(0) - , mGrabbedSceneNode(""), mOrigObjPos(Ogre::Vector3()), mOrigMousePos(Ogre::Vector3()) - , mOldMousePos(Ogre::Vector3()), mPlane(0) + , mGrabbedSceneNode(""), mGrabbedRefId(""), mOrigObjPos(Ogre::Vector3()) + , mOrigMousePos(Ogre::Vector3()), mOldMousePos(Ogre::Vector3()), mPlane(0) , mColIndexPosX(0), mColIndexPosY(0), mColIndexPosZ(0), mIdTableModel(0) { const CSMWorld::RefCollection& references = mParent->mDocument.getData().getReferences(); @@ -120,16 +120,13 @@ namespace CSVRender std::pair planeResult = mousePosOnPlane(event->pos(), *mPlane); if(planeResult.first) { - if(mGrabbedSceneNode != "") - { - Ogre::Vector3 pos = mOrigObjPos + (planeResult.second-mOrigMousePos); + Ogre::Vector3 pos = mOrigObjPos + (planeResult.second-mOrigMousePos); - mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(pos); - mPhysics->moveSceneNodes(mGrabbedSceneNode, pos); - updateSceneWidgets(); + mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(pos); + mPhysics->moveSceneNodes(mGrabbedSceneNode, pos); + updateSceneWidgets(); - mOldMousePos = planeResult.second; - } + mOldMousePos = planeResult.second; } } break; @@ -157,11 +154,18 @@ namespace CSVRender { if(event->buttons() & Qt::RightButton) { - std::pair result = objectUnderCursor(event->x(), event->y()); + // get object or pathgrid + std::pair result = underCursor(event->x(), event->y(), + CSVRender::Element_Reference|CSVRender::Element_Pathgrid); + if(result.first == "") break; - mGrabbedSceneNode = result.first; + mGrabbedSceneNode = mPhysics->refIdToSceneNode(result.first, mSceneManager); + if(!mSceneManager->hasSceneNode(mGrabbedSceneNode)) + break; + + mGrabbedRefId = result.first; // ray test agaist the plane to get a starting position of the // mouse in relation to the object position std::pair planeRes = planeAxis(); @@ -190,7 +194,9 @@ namespace CSVRender { case Mouse_Grab: { - std::pair result = objectUnderCursor(event->x(), event->y()); + std::pair result = underCursor(event->x(), event->y(), + CSVRender::Element_Reference|CSVRender::Element_Pathgrid); + if(result.first != "") { if(result.first == mCurrentObj) @@ -208,11 +214,7 @@ namespace CSVRender } //#if 0 // print some debug info - std::string referenceId = mPhysics->sceneNodeToRefId(result.first); - if(referenceId != "") - std::cout << "result grab release: " << referenceId << std::endl; - else - std::cout << "result grab release: " << result.first << std::endl; + std::cout << "result grab release: " << result.first << std::endl; std::cout << " hit pos "+ QString::number(result.second.x).toStdString() + ", " + QString::number(result.second.y).toStdString() + ", " + QString::number(result.second.z).toStdString() << std::endl; @@ -224,24 +226,21 @@ namespace CSVRender { // final placement std::pair planeResult = mousePosOnPlane(event->pos(), *mPlane); - if(planeResult.first && mGrabbedSceneNode != "") + if(planeResult.first) { Ogre::Vector3 pos = mOrigObjPos + (planeResult.second-mOrigMousePos); - // use the saved scene node name since the physics model has not moved yet - std::string referenceId = mPhysics->sceneNodeToRefId(mGrabbedSceneNode); - - if(QString(referenceId.c_str()).contains(QRegExp("^Pathgrid"))) + // use the saved reference Id since the physics model has not moved yet + if(QString(mGrabbedRefId.c_str()).contains(QRegExp("^Pathgrid"))) { // FIXME: move pathgrid point, but don't save yet (need pathgrid // table feature & its data structure to be completed) // Also need to signal PathgridPoint object of change std::pair result = mPhysics->distToClosest(pos, getCamera(), 600); // snap - std::string refId = mPhysics->sceneNodeToRefId(result.first); if(result.first != "" && // don't allow pathgrid points under the cursor - !QString(refId.c_str()).contains(QRegExp("^Pathgrid"))) + !QString(result.first.c_str()).contains(QRegExp("^Pathgrid"))) { pos.z -= result.second; pos.z += 2; // arbitrary number, lift up slightly (maybe change the nif?) @@ -251,7 +250,7 @@ namespace CSVRender // the object is a pathgrid point at the begging and set // a flag?) placeObject(mGrabbedSceneNode, pos); // result.second - mParent->pathgridMoved(referenceId, pos); // result.second + mParent->pathgridMoved(mGrabbedRefId, pos); // result.second } else cancelDrag(); // FIXME: does not allow editing if terrain not visible @@ -260,22 +259,23 @@ namespace CSVRender { mParent->mDocument.getUndoStack().beginMacro (QObject::tr("Move Object")); mParent->mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*mIdTableModel, - mIdTableModel->getModelIndex(referenceId, mColIndexPosX), pos.x)); + mIdTableModel->getModelIndex(mGrabbedRefId, mColIndexPosX), pos.x)); mParent->mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*mIdTableModel, - mIdTableModel->getModelIndex(referenceId, mColIndexPosY), pos.y)); + mIdTableModel->getModelIndex(mGrabbedRefId, mColIndexPosY), pos.y)); mParent->mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*mIdTableModel, - mIdTableModel->getModelIndex(referenceId, mColIndexPosZ), pos.z)); + mIdTableModel->getModelIndex(mGrabbedRefId, mColIndexPosZ), pos.z)); mParent->mDocument.getUndoStack().endMacro(); } // FIXME: highlight current object? - //mCurrentObj = mGrabbedSceneNode; // FIXME: doesn't work? + //mCurrentObj = mGrabbedRefId; // FIXME: doesn't work? mCurrentObj = ""; // whether the object is selected mMouseState = Mouse_Edit; // reset states - mGrabbedSceneNode = ""; // id of the object + mGrabbedRefId = ""; // id of the object + mGrabbedSceneNode = ""; mOrigMousePos = Ogre::Vector3(); // starting pos of mouse in world space mOrigObjPos = Ogre::Vector3(); // starting pos of object in world space mOldMousePos = Ogre::Vector3(); // mouse pos to use in wheel event @@ -287,7 +287,9 @@ namespace CSVRender case Mouse_Default: { // probably terrain, check - std::pair result = terrainUnderCursor(event->x(), event->y()); + std::pair result = underCursor(event->x(), event->y(), + CSVRender::Element_Terrain); + if(result.first != "") { // FIXME: terrain editing goes here @@ -354,9 +356,10 @@ namespace CSVRender mousePos = planeResult.second; } - // Find the final world position of the cursor + // Find the final world position of the object Ogre::Vector3 finalPos = mOrigObjPos + (mousePos-mOrigMousePos); + // update Ogre and Bullet mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(finalPos); mPhysics->moveSceneNodes(mGrabbedSceneNode, finalPos); updateSceneWidgets(); @@ -396,6 +399,7 @@ namespace CSVRender mOldMousePos = Ogre::Vector3(); mOrigMousePos = Ogre::Vector3(); mOrigObjPos = Ogre::Vector3(); + mGrabbedRefId = ""; mGrabbedSceneNode = ""; mCurrentObj = ""; mOldCursorPos = QPoint(0, 0); @@ -444,21 +448,6 @@ namespace CSVRender } } - // FIXME: castRay converts referenceId to scene node name only to be re-converted - // here - investigate whether refactoring required - std::pair MouseState::pgPointUnderCursor(const int mouseX, const int mouseY) - { - std::pair result = objectUnderCursor(mouseX, mouseY); - std::string referenceId = mPhysics->sceneNodeToRefId(result.first); - if(result.first != "" && - referenceId != "" && QString(referenceId.c_str()).contains(QRegExp("^Pathgrid"))) - { - return std::make_pair(referenceId, result.second); - } - - return std::make_pair("", Ogre::Vector3()); - } - std::pair MouseState::mousePosOnPlane(const QPoint &pos, const Ogre::Plane &plane) { // using a really small value seems to mess up with the projections @@ -476,47 +465,8 @@ namespace CSVRender return std::make_pair(false, Ogre::Vector3()); // should only happen if the plane is too small } - std::pair MouseState::terrainUnderCursor(const int mouseX, const int mouseY) - { - std::pair result = anyUnderCursor(mouseX, mouseY); - if(result.first != "") - { - // FIXME: is there a better way to distinguish terrain from objects? - QString name = QString(result.first.c_str()); - if(name.contains(QRegExp("^HeightField"))) - { - return result; - } - } - - return std::make_pair("", Ogre::Vector3()); - } - - // NOTE: also returns pathgrids - std::pair MouseState::objectUnderCursor(const int mouseX, const int mouseY) - { - std::pair result = anyUnderCursor(mouseX, mouseY); - if(result.first != "") - { - // NOTE: anything not terrain is assumed to be an object, e.g pathgrid points - QString name = QString(result.first.c_str()); - if(!name.contains(QRegExp("^HeightField"))) - { - uint32_t visibilityMask = getViewport()->getVisibilityMask(); - bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); - bool ignorePathgrid = !(visibilityMask & (uint32_t)CSVRender::Element_Pathgrid); - - if((!ignoreObjects || !ignorePathgrid) && mSceneManager->hasSceneNode(result.first)) - { - return result; - } - } - } - - return std::make_pair("", Ogre::Vector3()); - } - - std::pair MouseState::anyUnderCursor(const int mouseX, const int mouseY) + std::pair MouseState::underCursor(const int mouseX, + const int mouseY, Ogre::uint32 elements) { if(!getViewport()) return std::make_pair("", Ogre::Vector3()); @@ -524,13 +474,7 @@ namespace CSVRender float x = (float) mouseX / getViewport()->getActualWidth(); float y = (float) mouseY / getViewport()->getActualHeight(); - std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); - if(result.first != "") - { - return result; - } - - return std::make_pair("", Ogre::Vector3()); + return mPhysics->castRay(x, y, mSceneManager, getCamera(), elements); } void MouseState::updateSceneWidgets() @@ -554,14 +498,12 @@ namespace CSVRender return mParent->getCamera()->getViewport(); } - void MouseState::placeObject(const std::string sceneNode, const Ogre::Vector3 &pos) + void MouseState::placeObject(const std::string sceneNodeName, const Ogre::Vector3 &pos) { - mSceneManager->getSceneNode(sceneNode)->setPosition(pos); + mSceneManager->getSceneNode(sceneNodeName)->setPosition(pos); // update physics - std::string refId = mPhysics->sceneNodeToRefId(sceneNode); - - mPhysics->replaceObject(sceneNode, 1, pos, Ogre::Quaternion::IDENTITY); + mPhysics->replaceObject(sceneNodeName, 1, pos, Ogre::Quaternion::IDENTITY); // update all SceneWidgets and their SceneManagers updateSceneWidgets(); diff --git a/apps/opencs/view/render/mousestate.hpp b/apps/opencs/view/render/mousestate.hpp index d21735a78..d55d5f6c3 100644 --- a/apps/opencs/view/render/mousestate.hpp +++ b/apps/opencs/view/render/mousestate.hpp @@ -49,6 +49,7 @@ namespace CSVRender QPoint mOldCursorPos; std::string mCurrentObj; std::string mGrabbedSceneNode; + std::string mGrabbedRefId; QElapsedTimer *mMouseEventTimer; Ogre::Plane *mPlane; Ogre::Vector3 mOrigObjPos; @@ -70,20 +71,19 @@ namespace CSVRender void mouseReleaseEvent (QMouseEvent *event); void mouseDoubleClickEvent (QMouseEvent *event); bool wheelEvent (QWheelEvent *event); - std::pair pgPointUnderCursor(const int mouseX, const int mouseY); - std::pair anyUnderCursor(const int mouseX, const int mouseY); + + std::pair underCursor(const int mouseX, + const int mouseY, Ogre::uint32 elements); void cancelDrag(); private: std::pair mousePosOnPlane(const QPoint &pos, const Ogre::Plane &plane); - std::pair terrainUnderCursor(const int mouseX, const int mouseY); - std::pair objectUnderCursor(const int mouseX, const int mouseY); std::pair planeAxis(); void updateSceneWidgets(); - void placeObject(const std::string sceneNode, const Ogre::Vector3 &pos); // FIXME + void placeObject(const std::string sceneNodeName, const Ogre::Vector3 &pos); // FIXME Ogre::Camera *getCamera(); // friend access Ogre::Viewport *getViewport(); // friend access diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 506fd54df..bb856dbff 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -334,10 +334,9 @@ CSVRender::Cell *CSVRender::PagedWorldspaceWidget::findCell(const std::string &c } // NOTE: allow placing pathgrid points above objects and terrain -void CSVRender::PagedWorldspaceWidget::pathgridInserted (const std::string &name, const Ogre::Vector3 &pos) +void CSVRender::PagedWorldspaceWidget::pathgridInserted (const std::string &referenceId, const Ogre::Vector3 &pos) { - QString id = QString(name.c_str()); - std::string referenceId = getPhysics()->sceneNodeToRefId(name); // FIXME: move back + QString id = QString(referenceId.c_str()); bool terrain = id.startsWith("HeightField_"); bool object = QString(referenceId.c_str()).startsWith("ref#"); diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index b5ca91880..63f6caa60 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -96,7 +96,7 @@ namespace CSVRender virtual void mouseDoubleClickEvent (QMouseEvent *event); // FIXME: temporary only until signals from the document is implemented - virtual void pathgridInserted (const std::string &terrain, const Ogre::Vector3 &pos); + virtual void pathgridInserted (const std::string &referenceId, const Ogre::Vector3 &pos); virtual void pathgridMoved (const std::string &pgName, const Ogre::Vector3 &pos); virtual void pathgridAboutToBeRemoved (const std::string &pgName); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 4f85f3a30..54d0ce8e8 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -411,7 +411,9 @@ void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) else if(event->key() == Qt::Key_Delete) { QPoint p = this->mapFromGlobal(QCursor::pos()); - std::pair result = mMouse->pgPointUnderCursor(p.x(), p.y()); + std::pair result = + mMouse->underCursor(p.x(), p.y(), CSVRender::Element_Pathgrid); + if(result.first != "") { pathgridAboutToBeRemoved(result.first); @@ -422,7 +424,9 @@ void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) else if(event->key() == Qt::Key_Insert) { QPoint p = this->mapFromGlobal(QCursor::pos()); - std::pair result = mMouse->anyUnderCursor(p.x(), p.y()); + std::pair result = + mMouse->underCursor(p.x(), p.y(), CSVRender::Element_Reference|CSVRender::Element_Terrain); + if(result.first != "") { pathgridInserted(result.first, result.second); diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index 9ff945829..9012a06c2 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -211,15 +211,11 @@ namespace CSVWorld } } - // sceneMgr: to lookup the scene node name from the object's referenceId - // camera: primarily used to get the visibility mask for the viewport - // - // returns the found object's scene node name and its position in the world space - // // WARNING: far clip distance is a global setting, if it changes in future // this method will need to be updated std::pair PhysicsSystem::castRay(float mouseX, - float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera) + float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera, + Ogre::uint32 elements) { // NOTE: there could be more than one camera for the scene manager // TODO: check whether camera belongs to sceneMgr @@ -241,10 +237,10 @@ namespace CSVWorld _from = btVector3(from.x, from.y, from.z); _to = btVector3(to.x, to.y, to.z); - uint32_t visibilityMask = camera->getViewport()->getVisibilityMask(); - bool ignoreHeightMap = !(visibilityMask & (uint32_t)CSVRender::Element_Terrain); - bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); - bool ignorePathgrid = !(visibilityMask & (uint32_t)CSVRender::Element_Pathgrid); + Ogre::uint32 visibilityMask = camera->getViewport()->getVisibilityMask(); + bool ignoreHeightMap = !(visibilityMask & (Ogre::uint32)CSVRender::Element_Terrain); + bool ignoreObjects = !(visibilityMask & (Ogre::uint32)CSVRender::Element_Reference); + bool ignorePathgrid = !(visibilityMask & (Ogre::uint32)CSVRender::Element_Pathgrid); std::pair result = std::make_pair("", -1); short mask = OEngine::Physic::CollisionType_Raycasting; @@ -265,20 +261,18 @@ namespace CSVWorld } // result.first is the object's referenceId - if(result.first == "") - return std::make_pair("", Ogre::Vector3()); - else + if(result.first != "" && + ((elements & (Ogre::uint32)CSVRender::Element_Terrain && + QString(result.first.c_str()).contains(QRegExp("^Height"))) || + (elements & (Ogre::uint32)CSVRender::Element_Reference && + QString(result.first.c_str()).contains(QRegExp("^ref#"))) || + (elements & (Ogre::uint32)CSVRender::Element_Pathgrid && + QString(result.first.c_str()).contains(QRegExp("^Pathgrid"))))) { - // FIXME: maybe below logic belongs in the caller, i.e. terrainUnderCursor or - // objectUnderCursor - std::string name = refIdToSceneNode(result.first, sceneMgr); - if(name == "") - name = result.first; // prob terrain - else - name = refIdToSceneNode(result.first, sceneMgr); // prob object - - return std::make_pair(name, ray.getPoint(farClipDist*result.second)); + return std::make_pair(result.first, ray.getPoint(farClipDist*result.second)); } + else + return std::make_pair("", Ogre::Vector3()); } std::pair PhysicsSystem::distToGround(const Ogre::Vector3 &position, @@ -288,11 +282,11 @@ namespace CSVWorld _from = btVector3(position.x, position.y, position.z); _to = btVector3(position.x, position.y, position.z-limit); - uint32_t visibilityMask = camera->getViewport()->getVisibilityMask(); - bool ignoreHeightMap = !(visibilityMask & (uint32_t)CSVRender::Element_Terrain); - bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); + Ogre::uint32 visibilityMask = camera->getViewport()->getVisibilityMask(); + bool ignoreHeightMap = !(visibilityMask & (Ogre::uint32)CSVRender::Element_Terrain); + bool ignoreObjects = !(visibilityMask & (Ogre::uint32)CSVRender::Element_Reference); bool ignorePathgrid = ignorePgPoint || - !(visibilityMask & (uint32_t)CSVRender::Element_Pathgrid); + !(visibilityMask & (Ogre::uint32)CSVRender::Element_Pathgrid); std::pair result = std::make_pair("", -1); short mask = OEngine::Physic::CollisionType_Raycasting; diff --git a/apps/opencs/view/world/physicssystem.hpp b/apps/opencs/view/world/physicssystem.hpp index 589dca9c5..5543c84b9 100644 --- a/apps/opencs/view/world/physicssystem.hpp +++ b/apps/opencs/view/world/physicssystem.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace Ogre { class Vector3; @@ -71,7 +73,8 @@ namespace CSVWorld // return the object's SceneNode name and position for the given SceneManager std::pair castRay(float mouseX, - float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera); + float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera, + Ogre::uint32 elements = 0xFFFFFFFF); std::pair distToGround(const Ogre::Vector3 &position, Ogre::Camera *camera, const float limit = 300000, bool ignorePgPoint = false); @@ -79,7 +82,7 @@ namespace CSVWorld std::pair distToClosest(const Ogre::Vector3 &position, Ogre::Camera *camera, const float limit = 100.0f); - std::string sceneNodeToRefId(std::string sceneNodeName); + std::string refIdToSceneNode(std::string referenceId, Ogre::SceneManager *sceneMgr); // for multi-scene manager per physics engine std::map sceneWidgets(); @@ -91,7 +94,7 @@ namespace CSVWorld void updateSelectionHighlight(std::string sceneNode, const Ogre::Vector3 &position); - std::string refIdToSceneNode(std::string referenceId, Ogre::SceneManager *sceneMgr); + std::string sceneNodeToRefId(std::string sceneNodeName); Ogre::SceneManager *findSceneManager(std::string sceneNodeName); }; From 8c6890c682e283b401dbd4286deada91822f94c5 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 12 Nov 2014 22:28:41 +1100 Subject: [PATCH 016/441] Move element filtering back out of castRay(). --- apps/opencs/view/render/mousestate.cpp | 18 ++++++++++++++++-- apps/opencs/view/render/mousestate.hpp | 2 +- apps/opencs/view/world/physicssystem.cpp | 24 +++++++----------------- apps/opencs/view/world/physicssystem.hpp | 7 +++---- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index 65596b752..e62a4b1ff 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -237,7 +237,8 @@ namespace CSVRender // table feature & its data structure to be completed) // Also need to signal PathgridPoint object of change std::pair result = - mPhysics->distToClosest(pos, getCamera(), 600); // snap + mPhysics->distToClosest(pos, + getCamera()->getViewport()->getVisibilityMask(), 600); // snap if(result.first != "" && // don't allow pathgrid points under the cursor !QString(result.first.c_str()).contains(QRegExp("^Pathgrid"))) @@ -474,7 +475,20 @@ namespace CSVRender float x = (float) mouseX / getViewport()->getActualWidth(); float y = (float) mouseY / getViewport()->getActualHeight(); - return mPhysics->castRay(x, y, mSceneManager, getCamera(), elements); + std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); + + if(result.first != "" && + ((elements & (Ogre::uint32)CSVRender::Element_Terrain && + QString(result.first.c_str()).contains(QRegExp("^Height"))) || + (elements & (Ogre::uint32)CSVRender::Element_Reference && + QString(result.first.c_str()).contains(QRegExp("^ref#"))) || + (elements & (Ogre::uint32)CSVRender::Element_Pathgrid && + QString(result.first.c_str()).contains(QRegExp("^Pathgrid"))))) + { + return result; + } + else + return std::make_pair("", Ogre::Vector3()); } void MouseState::updateSceneWidgets() diff --git a/apps/opencs/view/render/mousestate.hpp b/apps/opencs/view/render/mousestate.hpp index d55d5f6c3..cde53f2c5 100644 --- a/apps/opencs/view/render/mousestate.hpp +++ b/apps/opencs/view/render/mousestate.hpp @@ -73,7 +73,7 @@ namespace CSVRender bool wheelEvent (QWheelEvent *event); std::pair underCursor(const int mouseX, - const int mouseY, Ogre::uint32 elements); + const int mouseY, Ogre::uint32 elements = 0xFFFFFFFF); void cancelDrag(); diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index 9012a06c2..6450b5cf6 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -214,8 +214,7 @@ namespace CSVWorld // WARNING: far clip distance is a global setting, if it changes in future // this method will need to be updated std::pair PhysicsSystem::castRay(float mouseX, - float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera, - Ogre::uint32 elements) + float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera) { // NOTE: there could be more than one camera for the scene manager // TODO: check whether camera belongs to sceneMgr @@ -261,28 +260,19 @@ namespace CSVWorld } // result.first is the object's referenceId - if(result.first != "" && - ((elements & (Ogre::uint32)CSVRender::Element_Terrain && - QString(result.first.c_str()).contains(QRegExp("^Height"))) || - (elements & (Ogre::uint32)CSVRender::Element_Reference && - QString(result.first.c_str()).contains(QRegExp("^ref#"))) || - (elements & (Ogre::uint32)CSVRender::Element_Pathgrid && - QString(result.first.c_str()).contains(QRegExp("^Pathgrid"))))) - { - return std::make_pair(result.first, ray.getPoint(farClipDist*result.second)); - } - else + if(result.first == "") return std::make_pair("", Ogre::Vector3()); + else + return std::make_pair(result.first, ray.getPoint(farClipDist*result.second)); } std::pair PhysicsSystem::distToGround(const Ogre::Vector3 &position, - Ogre::Camera *camera, const float limit, bool ignorePgPoint) + Ogre::uint32 visibilityMask, const float limit, bool ignorePgPoint) { btVector3 _from, _to; _from = btVector3(position.x, position.y, position.z); _to = btVector3(position.x, position.y, position.z-limit); - Ogre::uint32 visibilityMask = camera->getViewport()->getVisibilityMask(); bool ignoreHeightMap = !(visibilityMask & (Ogre::uint32)CSVRender::Element_Terrain); bool ignoreObjects = !(visibilityMask & (Ogre::uint32)CSVRender::Element_Reference); bool ignorePathgrid = ignorePgPoint || @@ -315,13 +305,13 @@ namespace CSVWorld // tries to find the distance to the "top" of the closest object std::pair PhysicsSystem::distToClosest(const Ogre::Vector3 &position, - Ogre::Camera *camera, const float limit) + Ogre::uint32 visibilityMask, const float limit) { const float thickness = 50; // arbitrary number std::pair resDown = distToGround(Ogre::Vector3(position.x, position.y, position.z+thickness), - camera, limit+thickness, true); + visibilityMask, limit+thickness, true); if(resDown.first != "") return std::make_pair(resDown.first, resDown.second-thickness); diff --git a/apps/opencs/view/world/physicssystem.hpp b/apps/opencs/view/world/physicssystem.hpp index 5543c84b9..f71ab685c 100644 --- a/apps/opencs/view/world/physicssystem.hpp +++ b/apps/opencs/view/world/physicssystem.hpp @@ -73,14 +73,13 @@ namespace CSVWorld // return the object's SceneNode name and position for the given SceneManager std::pair castRay(float mouseX, - float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera, - Ogre::uint32 elements = 0xFFFFFFFF); + float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera); std::pair distToGround(const Ogre::Vector3 &position, - Ogre::Camera *camera, const float limit = 300000, bool ignorePgPoint = false); + Ogre::uint32 visibilityMask, const float limit = 300000, bool ignorePgPoint = false); std::pair distToClosest(const Ogre::Vector3 &position, - Ogre::Camera *camera, const float limit = 100.0f); + Ogre::uint32 visibilityMask, const float limit = 100.0f); std::string refIdToSceneNode(std::string referenceId, Ogre::SceneManager *sceneMgr); From 12511778d1d262365205b0c532c2d62e670a6ba2 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 12 Nov 2014 22:43:22 +1100 Subject: [PATCH 017/441] Remove duplicate parameter. --- apps/opencs/view/render/mousestate.cpp | 2 +- apps/opencs/view/world/physicssystem.cpp | 9 ++++----- apps/opencs/view/world/physicssystem.hpp | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index e62a4b1ff..41908d11c 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -244,7 +244,7 @@ namespace CSVRender !QString(result.first.c_str()).contains(QRegExp("^Pathgrid"))) { pos.z -= result.second; - pos.z += 2; // arbitrary number, lift up slightly (maybe change the nif?) + pos.z += 1; // arbitrary number, lift up slightly (maybe change the nif?) // FIXME: rather than just updating at the end, should // consider providing visual feedback of terrain height // while dragging the pathgrid point (maybe check whether diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index 6450b5cf6..c5f1d0a75 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -267,7 +267,7 @@ namespace CSVWorld } std::pair PhysicsSystem::distToGround(const Ogre::Vector3 &position, - Ogre::uint32 visibilityMask, const float limit, bool ignorePgPoint) + Ogre::uint32 visibilityMask, const float limit) { btVector3 _from, _to; _from = btVector3(position.x, position.y, position.z); @@ -275,8 +275,7 @@ namespace CSVWorld bool ignoreHeightMap = !(visibilityMask & (Ogre::uint32)CSVRender::Element_Terrain); bool ignoreObjects = !(visibilityMask & (Ogre::uint32)CSVRender::Element_Reference); - bool ignorePathgrid = ignorePgPoint || - !(visibilityMask & (Ogre::uint32)CSVRender::Element_Pathgrid); + bool ignorePathgrid = !(visibilityMask & (Ogre::uint32)CSVRender::Element_Pathgrid); std::pair result = std::make_pair("", -1); short mask = OEngine::Physic::CollisionType_Raycasting; @@ -303,7 +302,7 @@ namespace CSVWorld return std::make_pair(result.first, limit*result.second); } - // tries to find the distance to the "top" of the closest object + // tries to find the distance to the "top" of the closest object (ignores pathgrid points) std::pair PhysicsSystem::distToClosest(const Ogre::Vector3 &position, Ogre::uint32 visibilityMask, const float limit) { @@ -311,7 +310,7 @@ namespace CSVWorld std::pair resDown = distToGround(Ogre::Vector3(position.x, position.y, position.z+thickness), - visibilityMask, limit+thickness, true); + visibilityMask&(~CSVRender::Element_Pathgrid), limit+thickness); if(resDown.first != "") return std::make_pair(resDown.first, resDown.second-thickness); diff --git a/apps/opencs/view/world/physicssystem.hpp b/apps/opencs/view/world/physicssystem.hpp index f71ab685c..839d55654 100644 --- a/apps/opencs/view/world/physicssystem.hpp +++ b/apps/opencs/view/world/physicssystem.hpp @@ -76,7 +76,7 @@ namespace CSVWorld float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera); std::pair distToGround(const Ogre::Vector3 &position, - Ogre::uint32 visibilityMask, const float limit = 300000, bool ignorePgPoint = false); + Ogre::uint32 visibilityMask, const float limit = 300000); std::pair distToClosest(const Ogre::Vector3 &position, Ogre::uint32 visibilityMask, const float limit = 100.0f); From e430dcfd8ad1884cfab800e38874bcc1e854cfff Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 13 Nov 2014 05:59:04 +1100 Subject: [PATCH 018/441] Check the resource group's existence before destroying it. --- apps/opencs/view/render/cell.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 043515907..40cf904f8 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -42,8 +42,13 @@ void CSVRender::Cell::createGridMaterials() void CSVRender::Cell::destroyGridMaterials() { - if(!Ogre::MaterialManager::getSingleton().getByName(PG_LINE_MATERIAL, DEBUGGING_GROUP).isNull()) - Ogre::MaterialManager::getSingleton().remove(PG_LINE_MATERIAL); + if(Ogre::ResourceGroupManager::getSingleton().resourceGroupExists(DEBUGGING_GROUP)) + { + if(!Ogre::MaterialManager::getSingleton().getByName(PG_LINE_MATERIAL, DEBUGGING_GROUP).isNull()) + Ogre::MaterialManager::getSingleton().remove(PG_LINE_MATERIAL); + + Ogre::ResourceGroupManager::getSingleton().destroyResourceGroup(DEBUGGING_GROUP); + } } Ogre::ManualObject *CSVRender::Cell::createPathgridEdge(const std::string &name, @@ -165,7 +170,6 @@ CSVRender::Cell::~Cell() } } destroyGridMaterials(); - Ogre::ResourceGroupManager::getSingleton().destroyResourceGroup(DEBUGGING_GROUP); for(std::map::iterator iter (mPgPoints.begin()); iter!=mPgPoints.end(); ++iter) From 0e0ad97a9108580b9ee8c8472342d4624c98a6ad Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 13 Nov 2014 06:36:47 +1100 Subject: [PATCH 019/441] Pathgrid edge resource management for editing multiple cells or multiple documents. --- apps/opencs/view/render/cell.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 40cf904f8..32ef101e5 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -28,6 +28,9 @@ namespace CSVRender void CSVRender::Cell::createGridMaterials() { + if(!Ogre::ResourceGroupManager::getSingleton().resourceGroupExists(DEBUGGING_GROUP)) + Ogre::ResourceGroupManager::getSingleton().createResourceGroup(DEBUGGING_GROUP); + if(Ogre::MaterialManager::getSingleton().getByName(PG_LINE_MATERIAL, DEBUGGING_GROUP).isNull()) { Ogre::MaterialPtr lineMatPtr = @@ -56,6 +59,7 @@ Ogre::ManualObject *CSVRender::Cell::createPathgridEdge(const std::string &name, { Ogre::ManualObject *result = mSceneMgr->createManualObject(name); + createGridMaterials(); result->begin(PG_LINE_MATERIAL, Ogre::RenderOperation::OT_LINE_LIST); Ogre::Vector3 direction = (end - start); @@ -323,8 +327,6 @@ float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const // - repainting edges while moving void CSVRender::Cell::loadPathgrid() { - if(!Ogre::ResourceGroupManager::getSingleton().resourceGroupExists(DEBUGGING_GROUP)) - Ogre::ResourceGroupManager::getSingleton().createResourceGroup(DEBUGGING_GROUP); createGridMaterials(); int worldsize = ESM::Land::REAL_SIZE; From ba1a42ec05161d5816a8bf67855cf21ba4afc308 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 13 Nov 2014 07:41:31 +1100 Subject: [PATCH 020/441] Don't delete physics object if it was never created. --- apps/opencs/view/render/object.cpp | 6 ++++-- apps/opencs/view/render/object.hpp | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 21219db8f..e27371766 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -93,6 +93,7 @@ void CSVRender::Object::update() Ogre::Quaternion yr (Ogre::Radian (-reference.mPos.rot[1]), Ogre::Vector3::UNIT_Y); Ogre::Quaternion zr (Ogre::Radian (-reference.mPos.rot[2]), Ogre::Vector3::UNIT_Z); + mPhysicsObject = mReferenceId; mPhysics->addObject("meshes\\" + model, mBase->getName(), mReferenceId, reference.mScale, position, xr*yr*zr); } } @@ -134,7 +135,7 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const CSVRender::Object::Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode, const std::string& id, bool referenceable, CSVWorld::PhysicsSystem *physics, bool forceBaseToZero) -: mData (data), mBase (0), mForceBaseToZero (forceBaseToZero), mPhysics(physics) +: mData (data), mBase (0), mForceBaseToZero (forceBaseToZero), mPhysics(physics), mPhysicsObject("") { mBase = cellNode->createChildSceneNode(); @@ -156,7 +157,8 @@ CSVRender::Object::~Object() { clear(); - mPhysics->removeObject(mBase->getName()); + if(mPhysicsObject != "") + mPhysics->removeObject(mBase->getName()); if (mBase) mBase->getCreator()->destroySceneNode (mBase); diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index eba2dc814..8ce204e1c 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -32,6 +32,7 @@ namespace CSVRender NifOgre::ObjectScenePtr mObject; bool mForceBaseToZero; CSVWorld::PhysicsSystem *mPhysics; + std::string mPhysicsObject; /// Not implemented Object (const Object&); From 0a66877cf150a178b7cbc977ed553b2539083bca Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 13 Nov 2014 07:47:32 +1100 Subject: [PATCH 021/441] Remove no longer needed code. --- apps/opencs/view/render/cell.cpp | 2 -- apps/opencs/view/world/physicssystem.hpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 32ef101e5..12e00ca46 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -327,8 +327,6 @@ float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const // - repainting edges while moving void CSVRender::Cell::loadPathgrid() { - createGridMaterials(); - int worldsize = ESM::Land::REAL_SIZE; CSMWorld::SubCellCollection& pathgridCollection = mData.getPathgrids(); diff --git a/apps/opencs/view/world/physicssystem.hpp b/apps/opencs/view/world/physicssystem.hpp index 839d55654..6a634ed26 100644 --- a/apps/opencs/view/world/physicssystem.hpp +++ b/apps/opencs/view/world/physicssystem.hpp @@ -91,8 +91,6 @@ namespace CSVWorld void moveSceneNodeImpl(const std::string sceneNodeName, const std::string referenceId, const Ogre::Vector3 &position); - void updateSelectionHighlight(std::string sceneNode, const Ogre::Vector3 &position); - std::string sceneNodeToRefId(std::string sceneNodeName); Ogre::SceneManager *findSceneManager(std::string sceneNodeName); From 4c3c67422313ba747808eff5a6c3bc2882671c98 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 9 Dec 2014 19:37:37 +1100 Subject: [PATCH 022/441] Resolve merge issues. --- apps/opencs/view/render/pathgridpoint.cpp | 2 +- apps/opencs/view/render/pathgridpoint.hpp | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/pathgridpoint.cpp b/apps/opencs/view/render/pathgridpoint.cpp index 3e365932f..85b03beea 100644 --- a/apps/opencs/view/render/pathgridpoint.cpp +++ b/apps/opencs/view/render/pathgridpoint.cpp @@ -15,7 +15,7 @@ namespace CSVRender { PathgridPoint::PathgridPoint(const std::string &name, - Ogre::SceneNode *cellNode, const Ogre::Vector3 &pos, CSVWorld::PhysicsSystem *physics) + Ogre::SceneNode *cellNode, const Ogre::Vector3 &pos, boost::shared_ptr physics) : mBase(cellNode), mPhysics(physics) { mBase = cellNode->createChildSceneNode(); diff --git a/apps/opencs/view/render/pathgridpoint.hpp b/apps/opencs/view/render/pathgridpoint.hpp index 3419082c3..54fe70e31 100644 --- a/apps/opencs/view/render/pathgridpoint.hpp +++ b/apps/opencs/view/render/pathgridpoint.hpp @@ -1,6 +1,8 @@ #ifndef OPENCS_VIEW_PATHGRIDPOINT_H #define OPENCS_VIEW_PATHGRIDPOINT_H +#include + #include namespace Ogre @@ -19,14 +21,15 @@ namespace CSVRender { class PathgridPoint { - CSVWorld::PhysicsSystem *mPhysics; // local copy + boost::shared_ptr mPhysics; // local copy NifOgre::ObjectScenePtr mPgPoint; Ogre::SceneNode *mBase; public: PathgridPoint(const std::string &name, - Ogre::SceneNode *cellNode, const Ogre::Vector3 &pos, CSVWorld::PhysicsSystem *physics); + Ogre::SceneNode *cellNode, const Ogre::Vector3 &pos, + boost::shared_ptr physics); ~PathgridPoint(); From 68dbf929a2c024d0cbc3086ff15865a7125844af Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 18 Apr 2015 16:14:25 +1000 Subject: [PATCH 023/441] Add updated pathgrid marker nif file and fix merge issues. --- apps/opencs/view/render/cell.cpp | 3 +-- apps/opencs/view/render/cell.hpp | 3 --- files/materials/pathgrid_pt.nif | Bin 1683 -> 1484 bytes 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index d13649d3e..ef63909af 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -93,7 +93,7 @@ bool CSVRender::Cell::addObjects (int start, int end) bool modified = false; const CSMWorld::RefCollection& collection = mData.getReferences(); - + for (int i=start; i<=end; ++i) { std::string cell = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mCell); @@ -177,7 +177,6 @@ CSVRender::Cell::~Cell() if (mTerrain.get()) mPhysics->removeHeightField(mSceneMgr, mX, mY); - for (std::map::iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) delete iter->second; diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index e504fc7a4..0afd0920b 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -11,11 +11,8 @@ #ifndef Q_MOC_RUN #include -<<<<<<< .mine #include // FIXME: temporaty storage until saving to document -======= #endif ->>>>>>> .theirs #include "object.hpp" diff --git a/files/materials/pathgrid_pt.nif b/files/materials/pathgrid_pt.nif index 579b03036c05d5deefe564880dbe312ebbb671f2..cc37e0ee37ef732d4b42d2aadde343580bbd717c 100644 GIT binary patch literal 1484 zcmb7E%}&BV5FVrmA_{^(9C-jk(0K4*Bp5X@(ME&uYE~`Mq)1ci!5eQz;#ptA8;@Z0 zo9(V_q4+ao*>7gP`N^<@?Sv=2o)bJewPV-AS3l^9aId=Yl<1NZLQ53g^$PbCF-Oc^vyjVMW1kWN(b(dIEIfxuI-U3ZG45cx1L*o_58r#Ox7X7q|5D?r z9g0v?YoN z&n!&faL+Qw518|4&J^Z2)erZXh}o|tdPiEzQB}eewkS;&Ww6Cr%FzP0c##UUM9Wm9 z60KklDzr*#v`!m{_;ts>H^S1T)E(bn45}F?us&&v7+GArW7C^5|KClS^`c^S-Pi1i b@yz&tCynW4R1aw&3wjE7G*L@>^gI0m*A66H literal 1683 zcmb_c&2G~`5T2w?TS{o5<)=VFsl*Xg0afBs5fvdM4p>M~t z{C?Np?FC)$y&eQghexm&7u~;CWz{hD0=54Y+fzoWSk5~yRND7?35Oe)*tVL@XADTG z$msazOQxZWjz48KHiA7Fu|IzA>~=bx)-sfI<6e8A_tM49L(g%nZumu9vX(p%#5uQlEoZ8R#7-VYypD5R1EVB3_buexwNtrloXhIZ@iTF9 z{Hz^${+)}5IF;yZR(x^t`RvblZ2ox}hRvJ-a%RA%8BP(Knrxg>jG7~^xAk0x1sv9V z%5N;g-(S>Q9HU>GX1)qD24gYiESTIxGG_#)x;!zCin-NQ+-pZ_Xf6w~DA&+ju7hzy zmSsh5$}QZ@HMuQ!;I=7Xbdk!1z;9(1`L8re49DCSSsnHYnc}(&b DaQ$}< From 27a73a25e345307cfa92adc38b7990c69dc0c74b Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 23 Apr 2015 08:50:05 +1000 Subject: [PATCH 024/441] Saving to document via UndoStack implemented. --- apps/opencs/model/world/commands.cpp | 26 ++++ apps/opencs/model/world/commands.hpp | 26 +++- apps/opencs/model/world/idtree.cpp | 4 + .../model/world/nestedcoladapterimp.cpp | 6 + .../model/world/nestedcoladapterimp.hpp | 16 --- .../opencs/model/world/pathgridpointswrap.hpp | 26 ++++ apps/opencs/view/render/cell.cpp | 129 ++++++++++++------ apps/opencs/view/render/cell.hpp | 19 ++- .../view/render/pagedworldspacewidget.cpp | 2 +- .../view/render/unpagedworldspacewidget.cpp | 4 +- 10 files changed, 186 insertions(+), 72 deletions(-) create mode 100644 apps/opencs/model/world/pathgridpointswrap.hpp diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index d12c5d228..11a675db0 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -244,3 +244,29 @@ const CSMWorld::NestedTableWrapperBase& CSMWorld::NestedTableStoring::getOld() c { return *mOld; } + +// Current interface does not allow adding a non-blank row, so we're forced to modify +// the whole record. +CSMWorld::ModifyPathgridCommand::ModifyPathgridCommand(IdTree& model, + const std::string& id, int parentColumn, NestedTableWrapperBase* newRecord, QUndoCommand* parent) + : mModel(model), mId(id), mParentColumn(parentColumn), mRecord(newRecord) + , QUndoCommand(parent), NestedTableStoring(model, id, parentColumn) +{ + setText (("Modify Pathgrid record " + mId).c_str()); // FIXME: better description +} + +void CSMWorld::ModifyPathgridCommand::redo() +{ + const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); + + mModel.setNestedTable(parentIndex, *mRecord); +} + +void CSMWorld::ModifyPathgridCommand::undo() +{ + const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); + + mModel.setNestedTable(parentIndex, getOld()); + + // FIXME: needs to tell the cell to redraw, possibly using signals +} diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index a70b36178..4e4de25c7 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -168,7 +168,8 @@ namespace CSMWorld public: - DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); + DeleteNestedCommand (IdTree& model, + const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); virtual void redo(); @@ -187,7 +188,28 @@ namespace CSMWorld public: - AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); + AddNestedCommand(IdTree& model, + const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); + + virtual void redo(); + + virtual void undo(); + }; + + class ModifyPathgridCommand : public QUndoCommand, private NestedTableStoring + { + IdTree& mModel; + std::string mId; + + int mParentColumn; + + NestedTableWrapperBase* mRecord; + + public: + + // if newEdges is NULL, only the paths are updated + ModifyPathgridCommand(IdTree& model, const std::string& id, int parentColumn, + NestedTableWrapperBase* newRecord, QUndoCommand* parent = 0); virtual void redo(); diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 06db09a0f..3cdc58f4d 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -248,6 +248,10 @@ void CSMWorld::IdTree::setNestedTable(const QModelIndex& index, const CSMWorld:: { emit resetEnd(this->index(index.row(), 0).data().toString()); } + // FIXME: Removing pathgrid points will also remove pathgrid edges, but we have + // no way of emitting signals to indicate those changes. In addition, the + // removed edges can be any index (and there can be several edges removed + // but always by a factor of two) } CSMWorld::NestedTableWrapperBase* CSMWorld::IdTree::nestedTable(const QModelIndex& index) const diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index d29155a47..060a6bb39 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -6,6 +6,7 @@ #include "idcollection.hpp" #include "pathgrid.hpp" #include "info.hpp" +#include "pathgridpointswrap.hpp" namespace CSMWorld { @@ -147,6 +148,8 @@ namespace CSMWorld PathgridEdgeListAdapter::PathgridEdgeListAdapter () {} // ToDo: seems to be auto-sorted in the dialog table display after insertion + // + // FIXME: edges should be added in pairs void PathgridEdgeListAdapter::addRow(Record& record, int position) const { Pathgrid pathgrid = record.get(); @@ -168,6 +171,7 @@ namespace CSMWorld record.setModified (pathgrid); } + // FIXME: edges should be removed in pairs and Point.mConnectionNum updated void PathgridEdgeListAdapter::removeRow(Record& record, int rowToRemove) const { Pathgrid pathgrid = record.get(); @@ -218,6 +222,8 @@ namespace CSMWorld } // ToDo: detect duplicates in mEdges + // + // FIXME: Point.mConnectionNum needs to be updated void PathgridEdgeListAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 96dcd943d..2613d0f20 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -3,7 +3,6 @@ #include -#include #include #include // for converting magic effect id to string & back #include // for converting skill names @@ -23,21 +22,6 @@ namespace CSMWorld struct Pathgrid; struct Info; - struct PathgridPointsWrap : public NestedTableWrapperBase - { - ESM::Pathgrid mRecord; - - PathgridPointsWrap(ESM::Pathgrid pathgrid) - : mRecord(pathgrid) {} - - virtual ~PathgridPointsWrap() {} - - virtual int size() const - { - return mRecord.mPoints.size(); // used in IdTree::setNestedTable() - } - }; - class PathgridPointListAdapter : public NestedColumnAdapter { public: diff --git a/apps/opencs/model/world/pathgridpointswrap.hpp b/apps/opencs/model/world/pathgridpointswrap.hpp new file mode 100644 index 000000000..6f1f61fc6 --- /dev/null +++ b/apps/opencs/model/world/pathgridpointswrap.hpp @@ -0,0 +1,26 @@ +#ifndef CSM_WOLRD_PATHGRIDPOINTSWRAP_H +#define CSM_WOLRD_PATHGRIDPOINTSWRAP_H + +#include + +#include "nestedtablewrapper.hpp" + +namespace CSMWorld +{ + struct PathgridPointsWrap : public NestedTableWrapperBase + { + ESM::Pathgrid mRecord; + + PathgridPointsWrap(ESM::Pathgrid pathgrid) + : mRecord(pathgrid) {} + + virtual ~PathgridPointsWrap() {} + + virtual int size() const + { + return mRecord.mPoints.size(); // used in IdTree::setNestedTable() + } + }; +} + +#endif // CSM_WOLRD_PATHGRIDPOINTSWRAP_H diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index ef63909af..698912344 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -8,11 +8,16 @@ #include #include +#include "../../model/doc/document.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/world/idtree.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" #include "../../model/world/refcollection.hpp" #include "../../model/world/pathgrid.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/pathgridpointswrap.hpp" +#include "../../model/world/nestedtableproxymodel.hpp" #include "../world/physicssystem.hpp" #include "elements.hpp" @@ -92,7 +97,7 @@ bool CSVRender::Cell::addObjects (int start, int end) { bool modified = false; - const CSMWorld::RefCollection& collection = mData.getReferences(); + const CSMWorld::RefCollection& collection = mDocument.getData().getReferences(); for (int i=start; i<=end; ++i) { @@ -104,7 +109,7 @@ bool CSVRender::Cell::addObjects (int start, int end) { std::string id = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mId); - mObjects.insert (std::make_pair (id, new Object (mData, mCellNode, id, false, mPhysics))); + mObjects.insert (std::make_pair (id, new Object (mDocument.getData(), mCellNode, id, false, mPhysics))); modified = true; } } @@ -112,29 +117,29 @@ bool CSVRender::Cell::addObjects (int start, int end) return modified; } -CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, +CSVRender::Cell::Cell (CSMDoc::Document& document, Ogre::SceneManager *sceneManager, const std::string& id, boost::shared_ptr physics, const Ogre::Vector3& origin) -: mData (data), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager), mPhysics(physics), mX(0), mY(0), - mPathgridId("") +: mDocument (document), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager) +, mPhysics(physics), mX(0), mY(0), mPgIndex(-1), mModel(0), mProxyModel(0)// ,mPathgridId("") { mCellNode = sceneManager->getRootSceneNode()->createChildSceneNode(); mCellNode->setPosition (origin); CSMWorld::IdTable& references = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_References)); int rows = references.rowCount(); addObjects (0, rows-1); - const CSMWorld::IdCollection& land = mData.getLand(); + const CSMWorld::IdCollection& land = mDocument.getData().getLand(); int landIndex = land.searchId(mId); if (landIndex != -1) { const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get(); if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT) { - mTerrain.reset(new Terrain::TerrainGrid(sceneManager, new TerrainStorage(mData), Element_Terrain, true, + mTerrain.reset(new Terrain::TerrainGrid(sceneManager, new TerrainStorage(mDocument.getData()), Element_Terrain, true, Terrain::Align_XY)); mTerrain->loadCell(esmLand->mX, esmLand->mY); @@ -174,6 +179,8 @@ CSVRender::Cell::~Cell() delete iter->second; } + delete mProxyModel; + if (mTerrain.get()) mPhysics->removeHeightField(mSceneMgr, mX, mY); @@ -217,7 +224,7 @@ bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { CSMWorld::IdTable& references = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_References)); int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); int cellColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); @@ -269,7 +276,7 @@ bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, for (std::map::iterator iter (ids.begin()); iter!=ids.end(); ++iter) { mObjects.insert (std::make_pair ( - iter->first, new Object (mData, mCellNode, iter->first, false, mPhysics))); + iter->first, new Object (mDocument.getData(), mCellNode, iter->first, false, mPhysics))); modified = true; } @@ -284,7 +291,7 @@ bool CSVRender::Cell::referenceAboutToBeRemoved (const QModelIndex& parent, int return false; CSMWorld::IdTable& references = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_References)); int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); @@ -323,14 +330,22 @@ void CSVRender::Cell::loadPathgrid() { int worldsize = ESM::Land::REAL_SIZE; - CSMWorld::SubCellCollection& pathgridCollection = mData.getPathgrids(); - int index = pathgridCollection.searchId(mId); + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + int index = pathgrids.searchId(mId); if(index != -1) { - const CSMWorld::Pathgrid &pathgrid = pathgridCollection.getRecord(index).get(); - mPathgridId = pathgrid.mId; // FIXME: temporary storage (should be document) + mPgIndex = index; // keep a copy to save from searching mId all the time + + int col = pathgrids.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + + mModel = dynamic_cast( + mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_Pathgrid)); + + mProxyModel = new CSMWorld::NestedTableProxyModel (mModel->index(mPgIndex, col), + CSMWorld::ColumnBase::Display_NestedHeader, mModel); + + const CSMWorld::Pathgrid &pathgrid = pathgrids.getRecord(index).get(); - mPoints.resize(pathgrid.mPoints.size()); std::vector::const_iterator iter = pathgrid.mPoints.begin(); for(index = 0; iter != pathgrid.mPoints.end(); ++iter, ++index) { @@ -340,7 +355,6 @@ void CSVRender::Cell::loadPathgrid() Ogre::Vector3(worldsize*mX+(*iter).mX, worldsize*mY+(*iter).mY, (*iter).mZ); mPgPoints.insert(std::make_pair(name, new PathgridPoint(name, mCellNode, pos, mPhysics))); - mPoints[index] = *iter; // FIXME: temporary storage (should be document) } for(ESM::Pathgrid::EdgeList::const_iterator it = pathgrid.mEdges.begin(); @@ -363,16 +377,17 @@ void CSVRender::Cell::loadPathgrid() node->attachObject(line); mPgEdges.insert(std::make_pair(std::make_pair(edge.mV0, edge.mV1), name)); - mEdges.push_back(*it); // FIXME: temporary storage (should be document) } } } // NOTE: pos is in world coordinates -// FIXME: save to the document void CSVRender::Cell::pathgridPointAdded(const Ogre::Vector3 &pos, bool interior) { - std::string name = PathgridPoint::getName(mId, mPoints.size()); + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + CSMWorld::Pathgrid pathgrid = pathgrids.getRecord(mPgIndex).get(); + + std::string name = PathgridPoint::getName(mId, pathgrid.mPoints.size()); // generate a new name mPgPoints.insert(std::make_pair(name, new PathgridPoint(name, mCellNode, pos, mPhysics))); @@ -389,12 +404,18 @@ void CSVRender::Cell::pathgridPointAdded(const Ogre::Vector3 &pos, bool interior ESM::Pathgrid::Point point(x, y, (int)pos.z); point.mConnectionNum = 0; - mPoints.push_back(point); - mPathgridId = mId; + pathgrid.mPoints.push_back(point); // FIXME: update other scene managers + + pathgrid.mData.mS2 += 1; // increment the number of points + + // FIXME: probably will crash if this cell is deleted and undo() is actioned afterwards + mDocument.getUndoStack().push(new CSMWorld::ModifyPathgridCommand(*mModel, + mProxyModel->getParentId(), mProxyModel->getParentColumn(), + new CSMWorld::PathgridPointsWrap(pathgrid))); + // emit signal here? } -// FIXME: save to the document void CSVRender::Cell::pathgridPointRemoved(const std::string &name) { std::pair result = PathgridPoint::getIdAndIndex(name); @@ -404,18 +425,21 @@ void CSVRender::Cell::pathgridPointRemoved(const std::string &name) std::string pathgridId = result.first; int index = result.second; + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + CSMWorld::Pathgrid pathgrid = pathgrids.getRecord(mPgIndex).get(); + // check if the point exists - if(index < 0 || mPathgridId != pathgridId || (unsigned int)index >= mPoints.size()) + if(index < 0 || (unsigned int)index >= pathgrid.mPoints.size()) return; - int numToDelete = mPoints[index].mConnectionNum * 2; // for sanity check later + int numToDelete = pathgrid.mPoints[index].mConnectionNum * 2; // for sanity check later int edgeCount = 0; // find edges to delete std::vector > edges; - for(unsigned i = 0; i < mEdges.size(); ++i) + for(unsigned i = 0; i < pathgrid.mEdges.size(); ++i) { - if(mEdges[i].mV0 == index || mEdges[i].mV1 == index) + if(pathgrid.mEdges[i].mV0 == index || pathgrid.mEdges[i].mV1 == index) { for(std::map, std::string>::iterator iter = mPgEdges.begin(); iter != mPgEdges.end(); ++iter) @@ -448,12 +472,12 @@ void CSVRender::Cell::pathgridPointRemoved(const std::string &name) mPgEdges.erase(*iter); // update document - assert(mPoints[(*iter).first].mConnectionNum > 0); - mPoints[(*iter).first].mConnectionNum -= 1; - for(unsigned i = mEdges.size() - 1; i > 0; --i) + assert(pathgrid.mPoints[(*iter).first].mConnectionNum > 0); + pathgrid.mPoints[(*iter).first].mConnectionNum -= 1; + for(unsigned i = pathgrid.mEdges.size() - 1; i > 0; --i) { - if(mEdges[i].mV0 == index || mEdges[i].mV1 == index) - mEdges.erase(mEdges.begin() + i); + if(pathgrid.mEdges[i].mV0 == index || pathgrid.mEdges[i].mV1 == index) + pathgrid.mEdges.erase(pathgrid.mEdges.begin() + i); } } } @@ -462,10 +486,10 @@ void CSVRender::Cell::pathgridPointRemoved(const std::string &name) { // WARNING: continue anyway? Or should this be an exception? std::cerr << "The no of edges del does not match the no of conn for: " - << mPathgridId + "_" + QString::number(index).toStdString() << std::endl; + << pathgridId + "_" + QString::number(index).toStdString() << std::endl; } - if(edgeCount || mPoints[index].mConnectionNum == 0) + if(edgeCount || pathgrid.mPoints[index].mConnectionNum == 0) { // remove the point delete mPgPoints[name]; @@ -477,6 +501,13 @@ void CSVRender::Cell::pathgridPointRemoved(const std::string &name) //mPoints.erase(mPoints.begin() + index); // WARNING: Can't erase because the index will change // FIXME: it should be possible to refresh indicies but that means index values // can't be stored in maps, names, etc + + + + + // FIXME: probably will crash if this cell is deleted and undo() is actioned afterwards + mDocument.getUndoStack().push(new CSMWorld::ModifyPathgridCommand(*mModel, + mProxyModel->getParentId(), mProxyModel->getParentColumn(), new CSMWorld::PathgridPointsWrap(pathgrid))); } // NOTE: newPos is in world coordinates @@ -490,8 +521,11 @@ void CSVRender::Cell::pathgridPointMoved(const std::string &name, std::string pathgridId = result.first; int index = result.second; + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + CSMWorld::Pathgrid pathgrid = pathgrids.getRecord(mPgIndex).get(); + // check if the point exists - if(index < 0 || mPathgridId != pathgridId || (unsigned int)index >= mPoints.size()) + if(index < 0 || (unsigned int)index >= pathgrid.mPoints.size()) return; int worldsize = ESM::Land::REAL_SIZE; @@ -504,17 +538,17 @@ void CSVRender::Cell::pathgridPointMoved(const std::string &name, y = y - (worldsize * mY); } - mPoints[index].mX = x; - mPoints[index].mY = y; - mPoints[index].mZ = newPos.z; + pathgrid.mPoints[index].mX = x; + pathgrid.mPoints[index].mY = y; + pathgrid.mPoints[index].mZ = newPos.z; // delete then recreate the edges - for(unsigned i = 0; i < mEdges.size(); ++i) + for(unsigned i = 0; i < pathgrid.mEdges.size(); ++i) { - if(mEdges[i].mV0 == index || mEdges[i].mV1 == index) + if(pathgrid.mEdges[i].mV0 == index || pathgrid.mEdges[i].mV1 == index) { std::ostringstream stream; - stream << pathgridId << "_" << mEdges[i].mV0 << " " << mEdges[i].mV1; + stream << pathgridId << "_" << pathgrid.mEdges[i].mV0 << " " << pathgrid.mEdges[i].mV1; std::string name = stream.str(); if(mSceneMgr->hasManualObject(name)) @@ -524,9 +558,9 @@ void CSVRender::Cell::pathgridPointMoved(const std::string &name, Ogre::SceneNode *node = manual->getParentSceneNode(); mSceneMgr->destroyManualObject(name); - if(mEdges[i].mV0 == index) + if(pathgrid.mEdges[i].mV0 == index) { - const ESM::Pathgrid::Point &p1 = mPoints[mEdges[i].mV1]; + const ESM::Pathgrid::Point &p1 = pathgrid.mPoints[pathgrid.mEdges[i].mV1]; Ogre::ManualObject *line = createPathgridEdge(name, newPos, @@ -534,9 +568,9 @@ void CSVRender::Cell::pathgridPointMoved(const std::string &name, line->setVisibilityFlags(Element_Pathgrid); node->attachObject(line); } - else if(mEdges[i].mV1 == index) + else if(pathgrid.mEdges[i].mV1 == index) { - const ESM::Pathgrid::Point &p0 = mPoints[mEdges[i].mV0]; + const ESM::Pathgrid::Point &p0 = pathgrid.mPoints[pathgrid.mEdges[i].mV0]; Ogre::ManualObject *line = createPathgridEdge(name, Ogre::Vector3(worldsize*mX+p0.mX, worldsize*mY+p0.mY, p0.mZ), @@ -547,6 +581,11 @@ void CSVRender::Cell::pathgridPointMoved(const std::string &name, } } } + + // FIXME: probably will crash if this cell is deleted and undo() is actioned afterwards + mDocument.getUndoStack().push(new CSMWorld::ModifyPathgridCommand(*mModel, + mProxyModel->getParentId(), mProxyModel->getParentColumn(), + new CSMWorld::PathgridPointsWrap(pathgrid))); } // FIXME: save to the document diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 0afd0920b..2658ff1ad 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -25,10 +25,17 @@ namespace Ogre class ManualObject; } +namespace CSMDoc +{ + class Document; +} + namespace CSMWorld { - class Data; + //class Data; class Pathgrid; + class NestedTableProxyModel; + class IdTree; } namespace CSVWorld @@ -42,16 +49,16 @@ namespace CSVRender class Cell { - CSMWorld::Data& mData; + CSMDoc::Document& mDocument; std::string mId; Ogre::SceneNode *mCellNode; std::map mObjects; std::map mPgPoints; std::map, std::string> mPgEdges; - ESM::Pathgrid::PointList mPoints; // FIXME: temporary storage until saving to document - ESM::Pathgrid::EdgeList mEdges; // FIXME: temporary storage until saving to document - std::string mPathgridId; // FIXME: temporary storage until saving to document + CSMWorld::NestedTableProxyModel *mProxyModel; + CSMWorld::IdTree *mModel; + int mPgIndex; std::auto_ptr mTerrain; boost::shared_ptr mPhysics; @@ -71,7 +78,7 @@ namespace CSVRender public: - Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, const std::string& id, + Cell (CSMDoc::Document& document, Ogre::SceneManager *sceneManager, const std::string& id, boost::shared_ptr physics, const Ogre::Vector3& origin = Ogre::Vector3 (0, 0, 0)); ~Cell(); diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 9c42e471e..cc00b9159 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -113,7 +113,7 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() if (index > 0 && cells.getRecord (index).mState!=CSMWorld::RecordBase::State_Deleted && mCells.find (*iter)==mCells.end()) { - Cell *cell = new Cell (mDocument.getData(), getSceneManager(), + Cell *cell = new Cell (mDocument, getSceneManager(), iter->getId (mWorldspace), mDocument.getPhysics()); mCells.insert (std::make_pair (*iter, cell)); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 237bfabf5..b5e9504f3 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -49,7 +49,7 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& update(); - mCell.reset (new Cell (document.getData(), getSceneManager(), mCellId, document.getPhysics())); + mCell.reset (new Cell (document, getSceneManager(), mCellId, document.getPhysics())); } void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, @@ -91,7 +91,7 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vectorgetId(); - mCell.reset (new Cell (getDocument().getData(), getSceneManager(), mCellId, getDocument().getPhysics())); + mCell.reset (new Cell (getDocument(), getSceneManager(), mCellId, getDocument().getPhysics())); update(); emit cellChanged(*data.begin()); From 1a31aecc2f1eb3599922e5b1595f982847edd723 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 23 Apr 2015 22:20:45 +1000 Subject: [PATCH 025/441] Undo rendering works, but not using signals yet. --- apps/opencs/model/world/commands.cpp | 9 +- apps/opencs/model/world/commands.hpp | 9 +- apps/opencs/view/render/cell.cpp | 260 +++++++++------------- apps/opencs/view/render/cell.hpp | 8 +- apps/opencs/view/render/pathgridpoint.cpp | 3 - 5 files changed, 125 insertions(+), 164 deletions(-) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 11a675db0..172211d7b 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -6,6 +6,7 @@ #include "idtree.hpp" #include #include "nestedtablewrapper.hpp" +#include "../../view/render/cell.hpp" CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent) @@ -248,8 +249,9 @@ const CSMWorld::NestedTableWrapperBase& CSMWorld::NestedTableStoring::getOld() c // Current interface does not allow adding a non-blank row, so we're forced to modify // the whole record. CSMWorld::ModifyPathgridCommand::ModifyPathgridCommand(IdTree& model, - const std::string& id, int parentColumn, NestedTableWrapperBase* newRecord, QUndoCommand* parent) - : mModel(model), mId(id), mParentColumn(parentColumn), mRecord(newRecord) + const std::string& id, int parentColumn, CSVRender::Cell *cell, + NestedTableWrapperBase* newRecord, QUndoCommand* parent) + : mModel(model), mId(id), mParentColumn(parentColumn), mRecord(newRecord), mCell(cell) , QUndoCommand(parent), NestedTableStoring(model, id, parentColumn) { setText (("Modify Pathgrid record " + mId).c_str()); // FIXME: better description @@ -268,5 +270,6 @@ void CSMWorld::ModifyPathgridCommand::undo() mModel.setNestedTable(parentIndex, getOld()); - // FIXME: needs to tell the cell to redraw, possibly using signals + mCell->clearPathgrid(); + mCell->buildPathgrid(); } diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 4e4de25c7..8661f987a 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -17,6 +17,11 @@ class QModelIndex; class QAbstractItemModel; +namespace CSVRender +{ + class Cell; +} + namespace CSMWorld { class IdTable; @@ -204,11 +209,13 @@ namespace CSMWorld int mParentColumn; NestedTableWrapperBase* mRecord; + CSVRender::Cell *mCell; public: // if newEdges is NULL, only the paths are updated - ModifyPathgridCommand(IdTree& model, const std::string& id, int parentColumn, + ModifyPathgridCommand(IdTree& model, + const std::string& id, int parentColumn, CSVRender::Cell *cell, NestedTableWrapperBase* newRecord, QUndoCommand* parent = 0); virtual void redo(); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 698912344..87f70b48e 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -120,7 +120,7 @@ bool CSVRender::Cell::addObjects (int start, int end) CSVRender::Cell::Cell (CSMDoc::Document& document, Ogre::SceneManager *sceneManager, const std::string& id, boost::shared_ptr physics, const Ogre::Vector3& origin) : mDocument (document), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager) -, mPhysics(physics), mX(0), mY(0), mPgIndex(-1), mModel(0), mProxyModel(0)// ,mPathgridId("") +, mPhysics(physics), mX(0), mY(0), mPgIndex(-1), mModel(0), mProxyModel(0) { mCellNode = sceneManager->getRootSceneNode()->createChildSceneNode(); mCellNode->setPosition (origin); @@ -153,32 +153,15 @@ CSVRender::Cell::Cell (CSMDoc::Document& document, Ogre::SceneManager *sceneMana } } - loadPathgrid(); + setupPathgrid(); + buildPathgrid(); } CSVRender::Cell::~Cell() { - // destroy manual objects - for(std::map, std::string>::iterator iter = mPgEdges.begin(); - iter != mPgEdges.end(); ++iter) - { - if(mSceneMgr->hasManualObject((*iter).second)) - { - Ogre::ManualObject *manual = mSceneMgr->getManualObject((*iter).second); - Ogre::SceneNode *node = manual->getParentSceneNode(); - mSceneMgr->destroyManualObject((*iter).second); - if(mSceneMgr->hasSceneNode(node->getName())) - mSceneMgr->destroySceneNode(node); - } - } + clearPathgrid(); destroyGridMaterials(); - for(std::map::iterator iter (mPgPoints.begin()); - iter!=mPgPoints.end(); ++iter) - { - delete iter->second; - } - delete mProxyModel; if (mTerrain.get()) @@ -322,62 +305,101 @@ float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const } // FIXME: -// - updating indicies, including mData // - adding edges (need the ability to select a pathgrid and highlight) // - save to document & signals // - repainting edges while moving -void CSVRender::Cell::loadPathgrid() +void CSVRender::Cell::setupPathgrid() { - int worldsize = ESM::Land::REAL_SIZE; - const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); int index = pathgrids.searchId(mId); if(index != -1) { - mPgIndex = index; // keep a copy to save from searching mId all the time - int col = pathgrids.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + mPgIndex = index; // keep a copy to save from searching mId all the time + mModel = dynamic_cast( mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_Pathgrid)); mProxyModel = new CSMWorld::NestedTableProxyModel (mModel->index(mPgIndex, col), CSMWorld::ColumnBase::Display_NestedHeader, mModel); - const CSMWorld::Pathgrid &pathgrid = pathgrids.getRecord(index).get(); + } +} - std::vector::const_iterator iter = pathgrid.mPoints.begin(); - for(index = 0; iter != pathgrid.mPoints.end(); ++iter, ++index) +void CSVRender::Cell::clearPathgrid() +{ + // destroy manual objects (edges) + for(std::map, std::string>::iterator iter = mPgEdges.begin(); + iter != mPgEdges.end(); ++iter) + { + if(mSceneMgr->hasManualObject((*iter).second)) { - std::string name = PathgridPoint::getName(pathgrid.mId, index); + Ogre::ManualObject *manual = mSceneMgr->getManualObject((*iter).second); + Ogre::SceneNode *node = manual->getParentSceneNode(); + mSceneMgr->destroyManualObject((*iter).second); + if(mSceneMgr->hasSceneNode(node->getName())) + mSceneMgr->destroySceneNode(node); + } + } + mPgEdges.clear(); - Ogre::Vector3 pos = - Ogre::Vector3(worldsize*mX+(*iter).mX, worldsize*mY+(*iter).mY, (*iter).mZ); + // destroy points + for(std::map::iterator iter (mPgPoints.begin()); + iter!=mPgPoints.end(); ++iter) + { + delete iter->second; + } + mPgPoints.clear(); +} - mPgPoints.insert(std::make_pair(name, new PathgridPoint(name, mCellNode, pos, mPhysics))); - } +// NOTE: getName() generates a string representation of mId+index to uniquely identify a +// pathgrid point. The trouble is that the index can change when a pathgrid point is deleted. +// Need a new way of uniquely identifying a pathgrid point. +// +// A workaround is to re-generate the pathgrids and edges each time a point is deleted or +// undo() is called (probably via a signal) +void CSVRender::Cell::buildPathgrid() +{ + if (!mModel) + return; - for(ESM::Pathgrid::EdgeList::const_iterator it = pathgrid.mEdges.begin(); - it != pathgrid.mEdges.end(); - ++it) - { - Ogre::SceneNode *node = mCellNode->createChildSceneNode(); - const ESM::Pathgrid::Edge &edge = *it; - const ESM::Pathgrid::Point &p0 = pathgrid.mPoints[edge.mV0]; - const ESM::Pathgrid::Point &p1 = pathgrid.mPoints[edge.mV1]; - - std::ostringstream stream; - stream << pathgrid.mId << "_" << edge.mV0 << " " << edge.mV1; - std::string name = stream.str(); - - Ogre::ManualObject *line = createPathgridEdge(name, - Ogre::Vector3(worldsize*mX+p0.mX, worldsize*mY+p0.mY, p0.mZ), - Ogre::Vector3(worldsize*mX+p1.mX, worldsize*mY+p1.mY, p1.mZ)); - line->setVisibilityFlags(Element_Pathgrid); - node->attachObject(line); - - mPgEdges.insert(std::make_pair(std::make_pair(edge.mV0, edge.mV1), name)); - } + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + const CSMWorld::Pathgrid &pathgrid = pathgrids.getRecord(mPgIndex).get(); + + int worldsize = ESM::Land::REAL_SIZE; + + std::vector::const_iterator iter = pathgrid.mPoints.begin(); + for(int index = 0; iter != pathgrid.mPoints.end(); ++iter, ++index) + { + std::string name = PathgridPoint::getName(pathgrid.mId, index); + + Ogre::Vector3 pos = + Ogre::Vector3(worldsize*mX+(*iter).mX, worldsize*mY+(*iter).mY, (*iter).mZ); + + mPgPoints.insert(std::make_pair(name, new PathgridPoint(name, mCellNode, pos, mPhysics))); + } + + for(ESM::Pathgrid::EdgeList::const_iterator it = pathgrid.mEdges.begin(); + it != pathgrid.mEdges.end(); + ++it) + { + Ogre::SceneNode *node = mCellNode->createChildSceneNode(); + const ESM::Pathgrid::Edge &edge = *it; + const ESM::Pathgrid::Point &p0 = pathgrid.mPoints[edge.mV0]; + const ESM::Pathgrid::Point &p1 = pathgrid.mPoints[edge.mV1]; + + std::ostringstream stream; + stream << pathgrid.mId << "_" << edge.mV0 << " " << edge.mV1; + std::string name = stream.str(); + + Ogre::ManualObject *line = createPathgridEdge(name, + Ogre::Vector3(worldsize*mX+p0.mX, worldsize*mY+p0.mY, p0.mZ), + Ogre::Vector3(worldsize*mX+p1.mX, worldsize*mY+p1.mY, p1.mZ)); + line->setVisibilityFlags(Element_Pathgrid); + node->attachObject(line); + + mPgEdges.insert(std::make_pair(std::make_pair(edge.mV0, edge.mV1), name)); } } @@ -411,7 +433,7 @@ void CSVRender::Cell::pathgridPointAdded(const Ogre::Vector3 &pos, bool interior // FIXME: probably will crash if this cell is deleted and undo() is actioned afterwards mDocument.getUndoStack().push(new CSMWorld::ModifyPathgridCommand(*mModel, - mProxyModel->getParentId(), mProxyModel->getParentColumn(), + mProxyModel->getParentId(), mProxyModel->getParentColumn(), this, new CSMWorld::PathgridPointsWrap(pathgrid))); // emit signal here? } @@ -433,81 +455,46 @@ void CSVRender::Cell::pathgridPointRemoved(const std::string &name) return; int numToDelete = pathgrid.mPoints[index].mConnectionNum * 2; // for sanity check later - int edgeCount = 0; + int deletedEdgeCount = 0; - // find edges to delete - std::vector > edges; - for(unsigned i = 0; i < pathgrid.mEdges.size(); ++i) + // update edge indicies to account for the deleted pathgrid point + std::vector::iterator iter = pathgrid.mEdges.begin(); + for (; iter != pathgrid.mEdges.end();) { - if(pathgrid.mEdges[i].mV0 == index || pathgrid.mEdges[i].mV1 == index) + if (((*iter).mV0 == index) || ((*iter).mV1 == index)) { - for(std::map, std::string>::iterator iter = mPgEdges.begin(); - iter != mPgEdges.end(); ++iter) - { - if((*iter).first.first == index || (*iter).first.second == index) - { - edges.push_back(std::make_pair((*iter).first.first, (*iter).first.second)); - } - } + iter = pathgrid.mEdges.erase(iter); + pathgrid.mPoints[index].mConnectionNum -= 1; + deletedEdgeCount++; // for sanity check later } - } - - // delete the edges - for(std::vector >::iterator iter = edges.begin(); - iter != edges.end(); ++iter) - { - std::string name = mPgEdges[*iter]; - if(mSceneMgr->hasManualObject(name)) + else { - // remove manual objects - Ogre::ManualObject *manual = mSceneMgr->getManualObject(name); - Ogre::SceneNode *node = manual->getParentSceneNode(); - mSceneMgr->destroyManualObject(name); - if(mSceneMgr->hasSceneNode(node->getName())) - mSceneMgr->destroySceneNode(node); + if ((*iter).mV0 > index) + (*iter).mV0--; - edgeCount++; // for sanity check later + if ((*iter).mV1 > index) + (*iter).mV1--; - // update map - mPgEdges.erase(*iter); - - // update document - assert(pathgrid.mPoints[(*iter).first].mConnectionNum > 0); - pathgrid.mPoints[(*iter).first].mConnectionNum -= 1; - for(unsigned i = pathgrid.mEdges.size() - 1; i > 0; --i) - { - if(pathgrid.mEdges[i].mV0 == index || pathgrid.mEdges[i].mV1 == index) - pathgrid.mEdges.erase(pathgrid.mEdges.begin() + i); - } + ++iter; } } + pathgrid.mPoints.erase(pathgrid.mPoints.begin()+index); + pathgrid.mData.mS2 -= 1; // decrement the number of points - if(edgeCount != numToDelete) + if(deletedEdgeCount != numToDelete) { // WARNING: continue anyway? Or should this be an exception? std::cerr << "The no of edges del does not match the no of conn for: " << pathgridId + "_" + QString::number(index).toStdString() << std::endl; } - if(edgeCount || pathgrid.mPoints[index].mConnectionNum == 0) - { - // remove the point - delete mPgPoints[name]; - mPgPoints.erase(name); - // FIXME: update other scene managers - } - - // store to document - //mPoints.erase(mPoints.begin() + index); // WARNING: Can't erase because the index will change - // FIXME: it should be possible to refresh indicies but that means index values - // can't be stored in maps, names, etc - - - - // FIXME: probably will crash if this cell is deleted and undo() is actioned afterwards mDocument.getUndoStack().push(new CSMWorld::ModifyPathgridCommand(*mModel, - mProxyModel->getParentId(), mProxyModel->getParentColumn(), new CSMWorld::PathgridPointsWrap(pathgrid))); + mProxyModel->getParentId(), mProxyModel->getParentColumn(), this, + new CSMWorld::PathgridPointsWrap(pathgrid))); + + clearPathgrid(); + buildPathgrid(); } // NOTE: newPos is in world coordinates @@ -542,50 +529,13 @@ void CSVRender::Cell::pathgridPointMoved(const std::string &name, pathgrid.mPoints[index].mY = y; pathgrid.mPoints[index].mZ = newPos.z; - // delete then recreate the edges - for(unsigned i = 0; i < pathgrid.mEdges.size(); ++i) - { - if(pathgrid.mEdges[i].mV0 == index || pathgrid.mEdges[i].mV1 == index) - { - std::ostringstream stream; - stream << pathgridId << "_" << pathgrid.mEdges[i].mV0 << " " << pathgrid.mEdges[i].mV1; - std::string name = stream.str(); - - if(mSceneMgr->hasManualObject(name)) - { - // remove manual objects - Ogre::ManualObject *manual = mSceneMgr->getManualObject(name); - Ogre::SceneNode *node = manual->getParentSceneNode(); - mSceneMgr->destroyManualObject(name); - - if(pathgrid.mEdges[i].mV0 == index) - { - const ESM::Pathgrid::Point &p1 = pathgrid.mPoints[pathgrid.mEdges[i].mV1]; - - Ogre::ManualObject *line = createPathgridEdge(name, - newPos, - Ogre::Vector3(worldsize*mX+p1.mX, worldsize*mY+p1.mY, p1.mZ)); - line->setVisibilityFlags(Element_Pathgrid); - node->attachObject(line); - } - else if(pathgrid.mEdges[i].mV1 == index) - { - const ESM::Pathgrid::Point &p0 = pathgrid.mPoints[pathgrid.mEdges[i].mV0]; - - Ogre::ManualObject *line = createPathgridEdge(name, - Ogre::Vector3(worldsize*mX+p0.mX, worldsize*mY+p0.mY, p0.mZ), - newPos); - line->setVisibilityFlags(Element_Pathgrid); - node->attachObject(line); - } - } - } - } - // FIXME: probably will crash if this cell is deleted and undo() is actioned afterwards mDocument.getUndoStack().push(new CSMWorld::ModifyPathgridCommand(*mModel, - mProxyModel->getParentId(), mProxyModel->getParentColumn(), + mProxyModel->getParentId(), mProxyModel->getParentColumn(), this, new CSMWorld::PathgridPointsWrap(pathgrid))); + + clearPathgrid(); + buildPathgrid(); } // FIXME: save to the document diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 2658ff1ad..fac4ded59 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -11,7 +11,6 @@ #ifndef Q_MOC_RUN #include -#include // FIXME: temporaty storage until saving to document #endif #include "object.hpp" @@ -116,12 +115,17 @@ namespace CSVRender // for drawing pathgrid points & lines void createGridMaterials(); void destroyGridMaterials(); - void loadPathgrid(); + void setupPathgrid(); Ogre::ManualObject *createPathgridEdge(const std::string &name, const Ogre::Vector3 &start, const Ogre::Vector3 &end); void addPathgridEdge(); void removePathgridEdge(); + + public: + + void clearPathgrid(); + void buildPathgrid(); }; } diff --git a/apps/opencs/view/render/pathgridpoint.cpp b/apps/opencs/view/render/pathgridpoint.cpp index 85b03beea..ea6815fbe 100644 --- a/apps/opencs/view/render/pathgridpoint.cpp +++ b/apps/opencs/view/render/pathgridpoint.cpp @@ -1,13 +1,10 @@ #include "pathgridpoint.hpp" -#include // FIXME - #include #include #include -//#include "../../model/world/pathgrid.hpp" #include "../world/physicssystem.hpp" #include "elements.hpp" From e7bd29873922d8788422e3da83cd3a668feeb0c3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 24 Apr 2015 01:28:49 +1000 Subject: [PATCH 026/441] Undo now working via signals. --- apps/opencs/CMakeLists.txt | 3 +- apps/opencs/model/world/commands.cpp | 29 --------- apps/opencs/model/world/commands.hpp | 29 +-------- apps/opencs/model/world/pathgridcommands.cpp | 48 ++++++++++++++ apps/opencs/model/world/pathgridcommands.hpp | 65 +++++++++++++++++++ apps/opencs/view/render/cell.cpp | 42 ++++++++---- apps/opencs/view/render/cell.hpp | 4 +- .../view/render/pagedworldspacewidget.cpp | 7 ++ .../view/render/pagedworldspacewidget.hpp | 1 + .../view/render/unpagedworldspacewidget.cpp | 14 +++- .../view/render/unpagedworldspacewidget.hpp | 2 + 11 files changed, 169 insertions(+), 75 deletions(-) create mode 100644 apps/opencs/model/world/pathgridcommands.cpp create mode 100644 apps/opencs/model/world/pathgridcommands.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 5822b433a..35d5e5d3c 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -18,7 +18,8 @@ opencs_hdrs_noqt (model/doc opencs_units (model/world - idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree + idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable + nestedtableproxymodel idtree pathgridcommands ) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 172211d7b..d12c5d228 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -6,7 +6,6 @@ #include "idtree.hpp" #include #include "nestedtablewrapper.hpp" -#include "../../view/render/cell.hpp" CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent) @@ -245,31 +244,3 @@ const CSMWorld::NestedTableWrapperBase& CSMWorld::NestedTableStoring::getOld() c { return *mOld; } - -// Current interface does not allow adding a non-blank row, so we're forced to modify -// the whole record. -CSMWorld::ModifyPathgridCommand::ModifyPathgridCommand(IdTree& model, - const std::string& id, int parentColumn, CSVRender::Cell *cell, - NestedTableWrapperBase* newRecord, QUndoCommand* parent) - : mModel(model), mId(id), mParentColumn(parentColumn), mRecord(newRecord), mCell(cell) - , QUndoCommand(parent), NestedTableStoring(model, id, parentColumn) -{ - setText (("Modify Pathgrid record " + mId).c_str()); // FIXME: better description -} - -void CSMWorld::ModifyPathgridCommand::redo() -{ - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - - mModel.setNestedTable(parentIndex, *mRecord); -} - -void CSMWorld::ModifyPathgridCommand::undo() -{ - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - - mModel.setNestedTable(parentIndex, getOld()); - - mCell->clearPathgrid(); - mCell->buildPathgrid(); -} diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 8661f987a..0cd8a00f8 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -12,16 +12,11 @@ #include #include "universalid.hpp" -#include "nestedtablewrapper.hpp" +//#include "nestedtablewrapper.hpp" class QModelIndex; class QAbstractItemModel; -namespace CSVRender -{ - class Cell; -} - namespace CSMWorld { class IdTable; @@ -200,28 +195,6 @@ namespace CSMWorld virtual void undo(); }; - - class ModifyPathgridCommand : public QUndoCommand, private NestedTableStoring - { - IdTree& mModel; - std::string mId; - - int mParentColumn; - - NestedTableWrapperBase* mRecord; - CSVRender::Cell *mCell; - - public: - - // if newEdges is NULL, only the paths are updated - ModifyPathgridCommand(IdTree& model, - const std::string& id, int parentColumn, CSVRender::Cell *cell, - NestedTableWrapperBase* newRecord, QUndoCommand* parent = 0); - - virtual void redo(); - - virtual void undo(); - }; } #endif diff --git a/apps/opencs/model/world/pathgridcommands.cpp b/apps/opencs/model/world/pathgridcommands.cpp new file mode 100644 index 000000000..356806dcd --- /dev/null +++ b/apps/opencs/model/world/pathgridcommands.cpp @@ -0,0 +1,48 @@ +#include "pathgridcommands.hpp" + +#include "../../view/render/cell.hpp" +#include "idtree.hpp" +#include "nestedtablewrapper.hpp" + +// Current interface does not allow adding a non-blank row, so we're forced to modify +// the whole record. +CSMWorld::ModifyPathgridCommand::ModifyPathgridCommand(IdTree& model, + const std::string& id, int parentColumn, NestedTableWrapperBase* newRecord, QUndoCommand* parent) + : mModel(model), mId(id), mParentColumn(parentColumn), mRecord(newRecord) + , QUndoCommand(parent), NestedTableStoring(model, id, parentColumn) +{ + setText (("Modify Pathgrid record " + mId).c_str()); // FIXME: better description +} + +void CSMWorld::ModifyPathgridCommand::redo() +{ + const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); + + mModel.setNestedTable(parentIndex, *mRecord); +} + +void CSMWorld::ModifyPathgridCommand::undo() +{ + const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); + + mModel.setNestedTable(parentIndex, getOld()); + + emit undoActioned(); +} + +void CSMWorld::SignalHandler::rebuildPathgrid() +{ + mParent->clearPathgrid(); + mParent->buildPathgrid(); + + emit flagAsModified(); +} + +CSMWorld::SignalHandler::SignalHandler (CSVRender::Cell *parent) : mParent(parent) +{} + +void CSMWorld::SignalHandler::connectToCommand(const CSMWorld::ModifyPathgridCommand *command) +{ + connect (command, SIGNAL(undoActioned()), + this, SLOT(rebuildPathgrid())); +} diff --git a/apps/opencs/model/world/pathgridcommands.hpp b/apps/opencs/model/world/pathgridcommands.hpp new file mode 100644 index 000000000..335e3a893 --- /dev/null +++ b/apps/opencs/model/world/pathgridcommands.hpp @@ -0,0 +1,65 @@ +#ifndef CSM_WOLRD_PATHGRIDCOMMANDS_H +#define CSM_WOLRD_PATHGRIDCOMMANDS_H + +#include + +#include "commands.hpp" + +namespace CSVRender +{ + class Cell; +} + +namespace CSMWorld +{ + class IdTree; + class NestedTableWrapperBase; + + class ModifyPathgridCommand : public QObject, public QUndoCommand, private NestedTableStoring + { + Q_OBJECT + + IdTree& mModel; + std::string mId; + + int mParentColumn; + + NestedTableWrapperBase* mRecord; + + public: + + ModifyPathgridCommand(IdTree& model, + const std::string& id, int parentColumn, NestedTableWrapperBase* newRecord, + QUndoCommand* parent = 0); + + virtual void redo(); + + virtual void undo(); + + signals: + + void undoActioned(); + }; + + class SignalHandler : public QObject + { + Q_OBJECT + + CSVRender::Cell *mParent; + + public: + + SignalHandler (CSVRender::Cell *parent); + + void connectToCommand(const ModifyPathgridCommand *command); + + private slots: + + void rebuildPathgrid(); + + signals: + + void flagAsModified(); + }; +} +#endif // CSM_WOLRD_PATHGRIDCOMMANDS_H diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 87f70b48e..7976fd55e 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -16,6 +16,7 @@ #include "../../model/world/refcollection.hpp" #include "../../model/world/pathgrid.hpp" #include "../../model/world/commands.hpp" +#include "../../model/world/pathgridcommands.hpp" #include "../../model/world/pathgridpointswrap.hpp" #include "../../model/world/nestedtableproxymodel.hpp" #include "../world/physicssystem.hpp" @@ -119,8 +120,9 @@ bool CSVRender::Cell::addObjects (int start, int end) CSVRender::Cell::Cell (CSMDoc::Document& document, Ogre::SceneManager *sceneManager, const std::string& id, boost::shared_ptr physics, const Ogre::Vector3& origin) -: mDocument (document), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager) -, mPhysics(physics), mX(0), mY(0), mPgIndex(-1), mModel(0), mProxyModel(0) + : mDocument (document), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager) + , mPhysics(physics), mX(0), mY(0), mPgIndex(-1), mModel(0), mProxyModel(0) + , mHandler(new CSMWorld::SignalHandler(this)) { mCellNode = sceneManager->getRootSceneNode()->createChildSceneNode(); mCellNode->setPosition (origin); @@ -163,6 +165,7 @@ CSVRender::Cell::~Cell() destroyGridMaterials(); delete mProxyModel; + delete mHandler; if (mTerrain.get()) mPhysics->removeHeightField(mSceneMgr, mX, mY); @@ -431,10 +434,12 @@ void CSVRender::Cell::pathgridPointAdded(const Ogre::Vector3 &pos, bool interior pathgrid.mData.mS2 += 1; // increment the number of points - // FIXME: probably will crash if this cell is deleted and undo() is actioned afterwards - mDocument.getUndoStack().push(new CSMWorld::ModifyPathgridCommand(*mModel, - mProxyModel->getParentId(), mProxyModel->getParentColumn(), this, - new CSMWorld::PathgridPointsWrap(pathgrid))); + // FIXME: possible issue if this cell is deleted and undo() is actioned afterwards + CSMWorld::ModifyPathgridCommand *cmd = new CSMWorld::ModifyPathgridCommand(*mModel, + mProxyModel->getParentId(), mProxyModel->getParentColumn(), + new CSMWorld::PathgridPointsWrap(pathgrid)); + mHandler->connectToCommand(cmd); + mDocument.getUndoStack().push(cmd); // emit signal here? } @@ -488,10 +493,12 @@ void CSVRender::Cell::pathgridPointRemoved(const std::string &name) << pathgridId + "_" + QString::number(index).toStdString() << std::endl; } - // FIXME: probably will crash if this cell is deleted and undo() is actioned afterwards - mDocument.getUndoStack().push(new CSMWorld::ModifyPathgridCommand(*mModel, - mProxyModel->getParentId(), mProxyModel->getParentColumn(), this, - new CSMWorld::PathgridPointsWrap(pathgrid))); + // FIXME: possible issue if this cell is deleted and undo() is actioned afterwards + CSMWorld::ModifyPathgridCommand *cmd = new CSMWorld::ModifyPathgridCommand(*mModel, + mProxyModel->getParentId(), mProxyModel->getParentColumn(), + new CSMWorld::PathgridPointsWrap(pathgrid)); + mHandler->connectToCommand(cmd); + mDocument.getUndoStack().push(cmd); clearPathgrid(); buildPathgrid(); @@ -529,10 +536,12 @@ void CSVRender::Cell::pathgridPointMoved(const std::string &name, pathgrid.mPoints[index].mY = y; pathgrid.mPoints[index].mZ = newPos.z; - // FIXME: probably will crash if this cell is deleted and undo() is actioned afterwards - mDocument.getUndoStack().push(new CSMWorld::ModifyPathgridCommand(*mModel, - mProxyModel->getParentId(), mProxyModel->getParentColumn(), this, - new CSMWorld::PathgridPointsWrap(pathgrid))); + // FIXME: possible issue if this cell is deleted and undo() is actioned afterwards + CSMWorld::ModifyPathgridCommand *cmd = new CSMWorld::ModifyPathgridCommand(*mModel, + mProxyModel->getParentId(), mProxyModel->getParentColumn(), + new CSMWorld::PathgridPointsWrap(pathgrid)); + mHandler->connectToCommand(cmd); + mDocument.getUndoStack().push(cmd); clearPathgrid(); buildPathgrid(); @@ -551,3 +560,8 @@ void CSVRender::Cell::addPathgridEdge() void CSVRender::Cell::removePathgridEdge() { } + +CSMWorld::SignalHandler *CSVRender::Cell::getSignalHandler() +{ + return mHandler; +} diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index fac4ded59..cc00ee1c8 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -31,10 +31,10 @@ namespace CSMDoc namespace CSMWorld { - //class Data; class Pathgrid; class NestedTableProxyModel; class IdTree; + class SignalHandler; } namespace CSVWorld @@ -58,6 +58,7 @@ namespace CSVRender CSMWorld::NestedTableProxyModel *mProxyModel; CSMWorld::IdTree *mModel; int mPgIndex; + CSMWorld::SignalHandler *mHandler; std::auto_ptr mTerrain; boost::shared_ptr mPhysics; @@ -126,6 +127,7 @@ namespace CSVRender void clearPathgrid(); void buildPathgrid(); + CSMWorld::SignalHandler *getSignalHandler(); }; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index cc00b9159..130f5c409 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -19,6 +19,7 @@ #include "../../model/world/tablemimedata.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/world/pathgridcommands.hpp" #include "../widget/scenetooltoggle.hpp" #include "../widget/scenetoolmode.hpp" @@ -115,6 +116,7 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() { Cell *cell = new Cell (mDocument, getSceneManager(), iter->getId (mWorldspace), mDocument.getPhysics()); + connect (cell->getSignalHandler(), SIGNAL(flagAsModified()), this, SLOT(flagAsModSlot())); mCells.insert (std::make_pair (*iter, cell)); float height = cell->getTerrainHeightAt(Ogre::Vector3( @@ -637,3 +639,8 @@ void CSVRender::PagedWorldspaceWidget::cellAdded (const QModelIndex& index, int if (adjustCells()) flagAsModified(); } + +void CSVRender::PagedWorldspaceWidget::flagAsModSlot () +{ + flagAsModified(); +} diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 45e5dd8f7..099b4db25 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -120,6 +120,7 @@ namespace CSVRender virtual void cellAdded (const QModelIndex& index, int start, int end); + virtual void flagAsModSlot(); }; } diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index b5e9504f3..c95a87d03 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -13,6 +13,7 @@ #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/tablemimedata.hpp" +#include "../../model/world/pathgridcommands.hpp" #include "../widget/scenetooltoggle.hpp" #include "../widget/scenetooltoggle2.hpp" @@ -49,7 +50,9 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& update(); - mCell.reset (new Cell (document, getSceneManager(), mCellId, document.getPhysics())); + Cell *cell = new Cell (document, getSceneManager(), mCellId, document.getPhysics()); + connect (cell->getSignalHandler(), SIGNAL(flagAsModified()), this, SLOT(flagAsModSlot())); + mCell.reset (cell); } void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, @@ -91,7 +94,9 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vectorgetId(); - mCell.reset (new Cell (getDocument(), getSceneManager(), mCellId, getDocument().getPhysics())); + Cell *cell = new Cell (getDocument(), getSceneManager(), mCellId, getDocument().getPhysics()); + connect (cell->getSignalHandler(), SIGNAL(flagAsModified()), this, SLOT(flagAsModSlot())); + mCell.reset (cell); update(); emit cellChanged(*data.begin()); @@ -212,3 +217,8 @@ CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget: return ignored; } } + +void CSVRender::UnpagedWorldspaceWidget::flagAsModSlot () +{ + flagAsModified(); +} diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 11bfaeca3..468c48739 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -76,6 +76,8 @@ namespace CSVRender void cellRowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); + virtual void flagAsModSlot(); + signals: void cellChanged(const CSMWorld::UniversalId& id); From 3102a175229982ae5db914fe327718776d3732aa Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 24 Apr 2015 04:00:49 +1000 Subject: [PATCH 027/441] Changes made to DialogueSubview pathgrid tables are now reflected on PagedWorldspaceWidget. --- apps/opencs/model/world/commands.hpp | 1 - apps/opencs/model/world/pathgridcommands.cpp | 4 +-- apps/opencs/model/world/pathgridcommands.hpp | 2 +- apps/opencs/view/render/cell.cpp | 33 ++++++++++++++++--- apps/opencs/view/render/cell.hpp | 2 ++ .../view/render/pagedworldspacewidget.cpp | 24 ++++---------- .../view/render/pagedworldspacewidget.hpp | 9 ++--- .../view/render/unpagedworldspacewidget.cpp | 22 +++---------- .../view/render/unpagedworldspacewidget.hpp | 8 ++--- apps/opencs/view/render/worldspacewidget.cpp | 15 +++------ apps/opencs/view/render/worldspacewidget.hpp | 6 +--- 11 files changed, 55 insertions(+), 71 deletions(-) diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 02d042487..2d63b66c9 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -12,7 +12,6 @@ #include #include "universalid.hpp" -//#include "nestedtablewrapper.hpp" class QModelIndex; class QAbstractItemModel; diff --git a/apps/opencs/model/world/pathgridcommands.cpp b/apps/opencs/model/world/pathgridcommands.cpp index 356806dcd..193ab4457 100644 --- a/apps/opencs/model/world/pathgridcommands.cpp +++ b/apps/opencs/model/world/pathgridcommands.cpp @@ -1,6 +1,7 @@ #include "pathgridcommands.hpp" #include "../../view/render/cell.hpp" + #include "idtree.hpp" #include "nestedtablewrapper.hpp" @@ -43,6 +44,5 @@ CSMWorld::SignalHandler::SignalHandler (CSVRender::Cell *parent) : mParent(paren void CSMWorld::SignalHandler::connectToCommand(const CSMWorld::ModifyPathgridCommand *command) { - connect (command, SIGNAL(undoActioned()), - this, SLOT(rebuildPathgrid())); + connect (command, SIGNAL(undoActioned()), this, SLOT(rebuildPathgrid())); } diff --git a/apps/opencs/model/world/pathgridcommands.hpp b/apps/opencs/model/world/pathgridcommands.hpp index 335e3a893..3acf0865b 100644 --- a/apps/opencs/model/world/pathgridcommands.hpp +++ b/apps/opencs/model/world/pathgridcommands.hpp @@ -53,7 +53,7 @@ namespace CSMWorld void connectToCommand(const ModifyPathgridCommand *command); - private slots: + public slots: void rebuildPathgrid(); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 7976fd55e..f7ec24a56 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -307,9 +307,34 @@ float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const return -std::numeric_limits::max(); } +void CSVRender::Cell::pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + CSMWorld::IdTree *pathgrids = dynamic_cast( + mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_Pathgrid)); + + int idColumn = pathgrids->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + int colPaths = pathgrids->findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + //int colEdges = pathgrids->findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); + + // FIXME: how to detect adds/deletes/modifies? + + for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + { + std::string cell = Misc::StringUtils::lowerCase (pathgrids->data ( + pathgrids->index (i, idColumn)).toString().toUtf8().constData()); + + if (cell==mId && colPaths >= topLeft.column() && colPaths <= bottomRight.column()) + { + if (!mModel) + setupPathgrid(); + + mHandler->rebuildPathgrid(); + } + } +} + // FIXME: // - adding edges (need the ability to select a pathgrid and highlight) -// - save to document & signals // - repainting edges while moving void CSVRender::Cell::setupPathgrid() { @@ -434,7 +459,7 @@ void CSVRender::Cell::pathgridPointAdded(const Ogre::Vector3 &pos, bool interior pathgrid.mData.mS2 += 1; // increment the number of points - // FIXME: possible issue if this cell is deleted and undo() is actioned afterwards + // TODO: check for possible issue if this cell is deleted and undo() is actioned afterwards CSMWorld::ModifyPathgridCommand *cmd = new CSMWorld::ModifyPathgridCommand(*mModel, mProxyModel->getParentId(), mProxyModel->getParentColumn(), new CSMWorld::PathgridPointsWrap(pathgrid)); @@ -493,7 +518,7 @@ void CSVRender::Cell::pathgridPointRemoved(const std::string &name) << pathgridId + "_" + QString::number(index).toStdString() << std::endl; } - // FIXME: possible issue if this cell is deleted and undo() is actioned afterwards + // TODO: check for possible issue if this cell is deleted and undo() is actioned afterwards CSMWorld::ModifyPathgridCommand *cmd = new CSMWorld::ModifyPathgridCommand(*mModel, mProxyModel->getParentId(), mProxyModel->getParentColumn(), new CSMWorld::PathgridPointsWrap(pathgrid)); @@ -536,7 +561,7 @@ void CSVRender::Cell::pathgridPointMoved(const std::string &name, pathgrid.mPoints[index].mY = y; pathgrid.mPoints[index].mZ = newPos.z; - // FIXME: possible issue if this cell is deleted and undo() is actioned afterwards + // TODO: check for possible issue if this cell is deleted and undo() is actioned afterwards CSMWorld::ModifyPathgridCommand *cmd = new CSMWorld::ModifyPathgridCommand(*mModel, mProxyModel->getParentId(), mProxyModel->getParentColumn(), new CSMWorld::PathgridPointsWrap(pathgrid)); diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index cc00ee1c8..dbf680dd4 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -111,6 +111,8 @@ namespace CSVRender const Ogre::Vector3 &newPos, bool interior = false); void pathgridPointRemoved(const std::string &name); + void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + private: // for drawing pathgrid points & lines diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 130f5c409..799dd1e7f 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -330,23 +330,13 @@ void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent flagAsModified(); } -//void CSVRender::PagedWorldspaceWidget::pathgridAdded (const QModelIndex& parent, -// int start, int end) -//{ -// // FIXME: -//} -// -//void CSVRender::PagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, -// const QModelIndex& bottomRight) -//{ -// // FIXME: -//} -// -//void CSVRender::PagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, -// int start, int end) -//{ -// // FIXME: -//} +void CSVRender::PagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + iter->second->pathgridDataChanged (topLeft, bottomRight); +} CSVRender::Cell *CSVRender::PagedWorldspaceWidget::findCell(const std::string &cellId) { diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 099b4db25..ea9449b2f 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -53,12 +53,6 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end); - //virtual void pathgridAdded (const QModelIndex& parent, int start, int end); - - //virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - - //virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end); - virtual std::string getStartupInstruction(); Cell *findCell(const std::string &cellId); @@ -103,7 +97,6 @@ namespace CSVRender virtual void mouseDoubleClickEvent (QMouseEvent *event); - // FIXME: temporary only until signals from the document is implemented virtual void pathgridInserted (const std::string &referenceId, const Ogre::Vector3 &pos); virtual void pathgridMoved (const std::string &pgName, const Ogre::Vector3 &pos); virtual void pathgridAboutToBeRemoved (const std::string &pgName); @@ -121,6 +114,8 @@ namespace CSVRender virtual void cellAdded (const QModelIndex& index, int start, int end); virtual void flagAsModSlot(); + + virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); }; } diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index c95a87d03..af121ec19 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -166,23 +166,11 @@ void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( tool->addButton (Element_Fog, "Fog"); } -//void CSVRender::UnpagedWorldspaceWidget::pathgridAdded (const QModelIndex& parent, -// int start, int end) -//{ -// // FIXME: -//} -// -//void CSVRender::UnpagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, -// const QModelIndex& bottomRight) -//{ -// // FIXME: -//} -// -//void CSVRender::UnpagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, -// int start, int end) -//{ -// // FIXME: -//} +void CSVRender::UnpagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + // FIXME: +} std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() { diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 468c48739..4e19efbf0 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -58,12 +58,6 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end); - //virtual void pathgridAdded (const QModelIndex& index, int start, int end); - - //virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - - //virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end); - virtual std::string getStartupInstruction(); protected: @@ -78,6 +72,8 @@ namespace CSVRender virtual void flagAsModSlot(); + virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + signals: void cellChanged(const CSMWorld::UniversalId& id); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 8418a32a6..2d690303c 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -56,15 +56,11 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); - //QAbstractItemModel *pathgrids = - //document.getData().getTableModel (CSMWorld::UniversalId::Type_Pathgrid); + QAbstractItemModel *pathgrids = + document.getData().getTableModel (CSMWorld::UniversalId::Type_Pathgrid); - //connect (pathgrids, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - //this, SLOT (pathgridAdded (const QModelIndex&, int, int))); - //connect (pathgrids, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - //this, SLOT (pathgridDataChanged (const QModelIndex&, const QModelIndex&))); - //connect (pathgrids, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - //this, SLOT (pathgridAboutToBeRemoved (const QModelIndex&, int, int))); + connect (pathgrids, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (pathgridDataChanged (const QModelIndex&, const QModelIndex&))); mPhysics = document.getPhysics(); // create physics if one doesn't exist mPhysics->addSceneManager(getSceneManager(), this); @@ -460,17 +456,14 @@ void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) SceneWidget::keyPressEvent(event); } -// FIXME: temporary until signals from the document are implemented void CSVRender::WorldspaceWidget::pathgridAboutToBeRemoved (const std::string &pgName) { } -// FIXME: temporary until signals from the document are implemented void CSVRender::WorldspaceWidget::pathgridMoved (const std::string &pgName, const Ogre::Vector3 &newPos) { } -// FIXME: temporary until signals from the document are implemented void CSVRender::WorldspaceWidget::pathgridInserted (const std::string &name, const Ogre::Vector3 &pos) { } diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index efd2ee8c0..a250547b3 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -163,11 +163,7 @@ namespace CSVRender void debugProfileAboutToBeRemoved (const QModelIndex& parent, int start, int end); - //virtual void pathgridAdded (const QModelIndex& index, int start, int end) = 0; - - //virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; - - //virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; + virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; protected slots: From 680e46441d395ea3567cb25b12c0834db4f25b4a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 12 May 2015 08:21:05 +1000 Subject: [PATCH 028/441] Feature #2533. Similar but less complete implementation PR was not accepted (see https://github.com/OpenMW/openmw/pull/276#issuecomment-57612802). - User preferences setting to save the window state (position, geometry, etc) at the time of exit. - User preferences setting to workaround some X window managers not keeping pre-maximised state. - Uses opencs.ini to store window states between sessions. --- apps/opencs/model/settings/usersettings.cpp | 25 ++++++ apps/opencs/model/settings/usersettings.hpp | 2 + apps/opencs/view/doc/operations.cpp | 1 + apps/opencs/view/doc/subview.cpp | 2 + apps/opencs/view/doc/view.cpp | 90 ++++++++++++++++++--- apps/opencs/view/doc/view.hpp | 10 +++ apps/opencs/view/doc/viewmanager.cpp | 21 +++++ 7 files changed, 139 insertions(+), 12 deletions(-) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 9e00b7d1a..491e70a88 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -143,6 +143,16 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() minWidth->setDefaultValue (325); minWidth->setRange (50, 10000); minWidth->setToolTip ("Minimum width of subviews."); + + Setting *saveState = createSetting (Type_CheckBox, "save-state", "Save window size and position"); + saveState->setDefaultValue ("true"); + saveState->setToolTip ("Remember window size and position between editing sessions."); + + Setting *saveX = createSetting (Type_CheckBox, "x-save-state-workaround", "X windows workaround"); + saveX->setDefaultValue ("false"); + saveX->setToolTip ("Some X window managers don't remember the windows state before being" + " maximized. In such environments exiting while maximized will correctly start in a maximized" + " window, but restoring back to the normal size won't work. Try this workaround."); } declareSection ("records", "Records"); @@ -458,6 +468,21 @@ QString CSMSettings::UserSettings::setting(const QString &viewKey, const QString return QString(); } +QVariant CSMSettings::UserSettings::value(const QString &viewKey, const QVariant &value) +{ + if(value != QVariant()) + { + mSettingDefinitions->setValue (viewKey, value); + return value; + } + else if(mSettingDefinitions->contains(viewKey)) + { + return mSettingDefinitions->value (viewKey); + } + + return QVariant(); +} + bool CSMSettings::UserSettings::hasSettingDefinitions (const QString &viewKey) const { return (mSettingDefinitions->contains (viewKey)); diff --git a/apps/opencs/model/settings/usersettings.hpp b/apps/opencs/model/settings/usersettings.hpp index 5188a9842..c4c5ea583 100644 --- a/apps/opencs/model/settings/usersettings.hpp +++ b/apps/opencs/model/settings/usersettings.hpp @@ -82,6 +82,8 @@ namespace CSMSettings { QString setting(const QString &viewKey, const QString &value = QString()); + QVariant value(const QString &viewKey, const QVariant &value = QVariant()); + private: void buildSettingModelDefaults(); diff --git a/apps/opencs/view/doc/operations.cpp b/apps/opencs/view/doc/operations.cpp index 7ee4b8726..934c94a96 100644 --- a/apps/opencs/view/doc/operations.cpp +++ b/apps/opencs/view/doc/operations.cpp @@ -19,6 +19,7 @@ CSVDoc::Operations::Operations() setVisible (false); setFixedHeight (widgetContainer->height()); setTitleBarWidget (new QWidget (this)); + setObjectName (QString("operations")); // needed to suppress warning while saving window state } void CSVDoc::Operations::setProgress (int current, int max, int type, int threads) diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index df1e7ee49..230812eb0 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -8,6 +8,8 @@ CSVDoc::SubView::SubView (const CSMWorld::UniversalId& id) /// \todo add a button to the title bar that clones this sub view setWindowTitle (QString::fromUtf8 (mUniversalId.toString().c_str())); + // set object name to suppress warning while saving window state + setObjectName (QString::fromUtf8 (mUniversalId.toString().c_str())); setAttribute(Qt::WA_DeleteOnClose); } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 5636fff94..02852ab52 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -32,6 +32,23 @@ void CSVDoc::View::closeEvent (QCloseEvent *event) event->ignore(); else { + if (mSaveWindowState) + { + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + if (isMaximized() && mXWorkaround) + { + userSettings.setDefinitions("window/maximized", (QStringList() << "true")); + userSettings.saveDefinitions(); // store previously saved geometry & state + } + else + { + userSettings.value("window/geometry", saveGeometry()); + userSettings.value("window/state", saveState()); + userSettings.setDefinitions("window/maximized", (QStringList() << "false")); + userSettings.saveDefinitions(); + } + } + // closeRequest() returns true if last document mViewManager.removeDocAndView(mDocument); } @@ -381,22 +398,35 @@ void CSVDoc::View::updateActions() CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), - mViewTotal (totalViews) + mViewTotal (totalViews), mSaveWindowState(false), mXWorkaround(false) { - int width = CSMSettings::UserSettings::instance().settingValue - ("window/default-width").toInt(); + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + mXWorkaround = userSettings.settingValue ("window/x-save-state-workaround").toStdString() == "true"; + mSaveWindowState = userSettings.setting ("window/save-state", "true").toStdString() == "true"; - int height = CSMSettings::UserSettings::instance().settingValue - ("window/default-height").toInt(); + // check if saved state should be used and whether it is the first time + if (mSaveWindowState && userSettings.hasSettingDefinitions ("window/maximized")) + { + restoreGeometry(userSettings.value("window/geometry").toByteArray()); + restoreState(userSettings.value("window/state").toByteArray()); - width = std::max(width, 300); - height = std::max(height, 300); + if (mXWorkaround && userSettings.settingValue ("window/maximized").toStdString() == "true") + setWindowState(windowState() | Qt::WindowMaximized); + } + else + { + int width = userSettings.settingValue ("window/default-width").toInt(); + int height = userSettings.settingValue ("window/default-height").toInt(); + + width = std::max(width, 300); + height = std::max(height, 300); - // trick to get the window decorations and their sizes - show(); - hide(); - resize (width - (frameGeometry().width() - geometry().width()), - height - (frameGeometry().height() - geometry().height())); + // trick to get the window decorations and their sizes + show(); + hide(); + resize (width - (frameGeometry().width() - geometry().width()), + height - (frameGeometry().height() - geometry().height())); + } mSubViewWindow.setDockOptions (QMainWindow::AllowNestedDocks); @@ -770,6 +800,12 @@ void CSVDoc::View::updateUserSetting (const QString &name, const QStringList &li if (name=="window/hide-subview") updateSubViewIndicies (0); + if (name == "window/save-state") + mSaveWindowState = list.at(0) == "true"; + + if (name == "window/x-save-state-workaround") + mXWorkaround = list.at(0) == "true"; + foreach (SubView *subView, mSubViews) { subView->updateUserSetting (name, list); @@ -815,3 +851,33 @@ void CSVDoc::View::closeRequest (SubView *subView) else if (mViewManager.closeRequest (this)) mViewManager.removeDocAndView (mDocument); } + +// for more reliable detetion of isMaximized(), see https://bugreports.qt.io/browse/QTBUG-30085 +void CSVDoc::View::saveWindowState() +{ + if (!isMaximized()) + { + // update but don't save to config file yet + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + userSettings.value("window/geometry", saveGeometry()); + userSettings.value("window/state", saveState()); + } +} + +// For X11 where Qt does not remember pre-maximised state +void CSVDoc::View::moveEvent (QMoveEvent *event) +{ + if (mXWorkaround && mSaveWindowState) + QMetaObject::invokeMethod(this, "saveWindowState", Qt::QueuedConnection); + + QMainWindow::moveEvent(event); +} + +// For X11 where Qt does not remember pre-maximised state +void CSVDoc::View::resizeEvent (QResizeEvent *event) +{ + if (mXWorkaround && mSaveWindowState) + QMetaObject::invokeMethod(this, "saveWindowState", Qt::QueuedConnection); + + QMainWindow::resizeEvent(event); +} diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 32d7159c2..398f4fa67 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -48,6 +48,9 @@ namespace CSVDoc QMainWindow mSubViewWindow; GlobalDebugProfileMenu *mGlobalDebugProfileMenu; + bool mSaveWindowState; + bool mXWorkaround; + // not implemented View (const View&); @@ -112,6 +115,11 @@ namespace CSVDoc /// Function called by view manager when user preferences are updated void updateEditorSetting (const QString &, const QString &); + protected: + + virtual void moveEvent(QMoveEvent * event); + virtual void resizeEvent(QResizeEvent * event); + signals: void newGameRequest(); @@ -228,6 +236,8 @@ namespace CSVDoc void stop(); void closeRequest (SubView *subView); + + void saveWindowState(); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 6362f9659..106f147f9 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -405,6 +405,27 @@ bool CSVDoc::ViewManager::removeDocument (CSVDoc::View *view) else remainingViews.push_back(*iter); } + + // FIXME: seems removeDocument() and closeRequest() are duplicated functionality + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + if (remainingViews.empty() && !mViews.empty() && + userSettings.settingValue ("window/save-state").toStdString() == "true") + { + if (userSettings.settingValue ("window/x-save-state-workaround").toStdString() == "true" + && mViews.back()->isMaximized()) + { + userSettings.setDefinitions("window/maximized", (QStringList() << "true")); + userSettings.saveDefinitions(); // store previously saved geometry & state + } + else + { + userSettings.value("window/geometry", mViews.back()->saveGeometry()); + userSettings.value("window/state", mViews.back()->saveState()); + userSettings.setDefinitions("window/maximized", (QStringList() << "false")); + userSettings.saveDefinitions(); + } + } + mDocumentManager.removeDocument(document); mViews = remainingViews; } From 65a88aaedb18c4df0514d78ec716348ceafa9e92 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 12 May 2015 23:47:36 +1000 Subject: [PATCH 029/441] Feature #2532. Global filter shortcuts for project::added and project::modified. - Initial global filter shortcuts are set in the user preferences setting. These are applied when a table is first opened. - Subsequent changes are done per table view via tickboxes next to the record filter box. --- apps/opencs/model/settings/usersettings.cpp | 15 +++- apps/opencs/view/world/table.cpp | 84 ++++++++++++++++++++- apps/opencs/view/world/table.hpp | 7 ++ apps/opencs/view/world/tablesubview.cpp | 23 +++++- 4 files changed, 122 insertions(+), 7 deletions(-) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 491e70a88..5eb795046 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -241,13 +241,26 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() Setting *lineNum = createSetting (Type_CheckBox, "show-linenum", "Show Line Numbers"); lineNum->setDefaultValue ("true"); lineNum->setToolTip ("Show line numbers to the left of the script editor window." - "The current row and column numbers of the text cursor are shown at the bottom."); + " The current row and column numbers of the text cursor are shown at the bottom."); Setting *monoFont = createSetting (Type_CheckBox, "mono-font", "Use monospace font"); monoFont->setDefaultValue ("true"); monoFont->setToolTip ("Whether to use monospaced fonts on script edit subview."); } + declareSection ("filter", "Global Filter"); + { + Setting *projAdded = createSetting (Type_CheckBox, "project-added", "Project::added initial value"); + projAdded->setDefaultValue ("false"); + projAdded->setToolTip ("Show records added by the project when a table is first opened." + " Other records are filterd out."); + + Setting *projModified = createSetting (Type_CheckBox, "project-modified", "Project::modified initial value"); + projModified->setDefaultValue ("false"); + projModified->setToolTip ("Show records modified by the project when a table is first opened." + " Other records are filterd out."); + } + { /****************************************************************** * There are three types of values: diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 5a650f98a..2314a9397 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -21,6 +21,10 @@ #include "../../model/world/tablemimedata.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" +#include "../../model/filter/parser.hpp" +#include "../../model/filter/andnode.hpp" +#include "../../model/filter/ornode.hpp" +#include "../../model/settings/usersettings.hpp" #include "recordstatusdelegate.hpp" #include "util.hpp" @@ -362,6 +366,24 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_EditRecord)); mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_View)); mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier | Qt::ControlModifier, Action_EditRecordAndClose)); + + CSMFilter::Parser parser(mDocument.getData()); + + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + if(userSettings.settingValue ("filter/project-added") == "true") + { + parser.parse("!string(\"Modified\", \"Added\")"); + mAdded = parser.getFilter(); + } + + if(userSettings.settingValue ("filter/project-modified") == "true") + { + parser.parse("!string(\"Modified\", \"Modified\")"); + mModified = parser.getFilter(); + } + + if(mAdded || mModified) + recordFilterChanged(boost::shared_ptr()); } void CSVWorld::Table::setEditLock (bool locked) @@ -517,8 +539,7 @@ void CSVWorld::Table::previewRecord() } } -void CSVWorld::Table::updateUserSetting - (const QString &name, const QStringList &list) +void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList &list) { if (name=="records/type-format" || name=="records/status-format") { @@ -626,7 +647,37 @@ void CSVWorld::Table::requestFocus (const std::string& id) void CSVWorld::Table::recordFilterChanged (boost::shared_ptr filter) { - mProxyModel->setFilter (filter); + mFilter = filter; + + boost::shared_ptr global; + if(mAdded && mModified) + { + std::vector > nodes; + nodes.push_back(mAdded); + nodes.push_back(mModified); + global = boost::shared_ptr(new CSMFilter::OrNode (nodes)); + } + else if(mAdded) + { + global = mAdded; + } + else if(mModified) + { + global = mModified; + } + + if(filter && global) + { + std::vector > nodes; + nodes.push_back(filter); + nodes.push_back(global); + boost::shared_ptr combined(new CSMFilter::AndNode (nodes)); + mProxyModel->setFilter (combined); + } + else if(global) + mProxyModel->setFilter (global); + else + mProxyModel->setFilter (filter); tableSizeUpdate(); selectionSizeUpdate(); } @@ -700,3 +751,30 @@ std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const return idToDrag; } +void CSVWorld::Table::globalFilterAddedChanged(int state) +{ + if(state == Qt::Checked && !mAdded) + { + CSMFilter::Parser parser(mDocument.getData()); + parser.parse("!string(\"Modified\", \"Added\")"); + mAdded = parser.getFilter(); + } + else if(state == Qt::Unchecked) + mAdded.reset(); + + recordFilterChanged(mFilter); +} + +void CSVWorld::Table::globalFilterModifiedChanged(int state) +{ + if(state == Qt::Checked && !mModified) + { + CSMFilter::Parser parser(mDocument.getData()); + parser.parse("!string(\"Modified\", \"Modified\")"); + mModified = parser.getFilter(); + } + else if(state == Qt::Unchecked) + mModified.reset(); + + recordFilterChanged(mFilter); +} diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 75161b8b6..e87828e2f 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -68,6 +68,10 @@ namespace CSVWorld CSMWorld::UniversalId mEditCellId; std::map mDoubleClickActions; + boost::shared_ptr mFilter; + boost::shared_ptr mAdded; + boost::shared_ptr mModified; + private: void contextMenuEvent (QContextMenuEvent *event); @@ -139,6 +143,9 @@ namespace CSVWorld void recordFilterChanged (boost::shared_ptr filter); void updateUserSetting (const QString &name, const QStringList &list); + + void globalFilterAddedChanged (int state); + void globalFilterModifiedChanged (int state); }; } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 729b6b8d7..2d3ec8cc5 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -1,11 +1,14 @@ #include "tablesubview.hpp" +#include +#include #include #include #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" +#include "../../model/settings/usersettings.hpp" #include "../filter/filterbox.hpp" #include "table.hpp" @@ -28,7 +31,19 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D mFilterBox = new CSVFilter::FilterBox (document.getData(), this); - layout->insertWidget (0, mFilterBox); + QHBoxLayout *hLayout = new QHBoxLayout; + QCheckBox *added = new QCheckBox("A"); + QCheckBox *modified = new QCheckBox("M"); + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + added->setCheckState( + userSettings.settingValue ("filter/project-added") == "true" ? Qt::Checked : Qt::Unchecked); + modified->setCheckState( + userSettings.settingValue ("filter/project-modified") == "true" ? Qt::Checked : Qt::Unchecked); + + hLayout->insertWidget(0,mFilterBox); + hLayout->insertWidget(1,added); + hLayout->insertWidget(2,modified); + layout->insertLayout (0, hLayout); QWidget *widget = new QWidget; @@ -44,6 +59,9 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect (mTable, SIGNAL (tableSizeChanged (int, int, int)), mBottom, SLOT (tableSizeChanged (int, int, int))); + connect(added, SIGNAL (stateChanged(int)), mTable, SLOT (globalFilterAddedChanged(int))); + connect(modified, SIGNAL (stateChanged(int)), mTable, SLOT (globalFilterModifiedChanged(int))); + mTable->tableSizeUpdate(); mTable->selectionSizeUpdate(); mTable->viewport()->installEventFilter(this); @@ -84,8 +102,7 @@ void CSVWorld::TableSubView::editRequest (const CSMWorld::UniversalId& id, const focusId (id, hint); } -void CSVWorld::TableSubView::updateUserSetting - (const QString &name, const QStringList &list) +void CSVWorld::TableSubView::updateUserSetting (const QString &name, const QStringList &list) { mTable->updateUserSetting(name, list); } From 60f963382b58d9a024e575f7708c03a94de4932c Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 14 May 2015 19:36:39 +1000 Subject: [PATCH 030/441] Feature #2532. Global filter shortcuts for project::added and project::modified. - Add a startup warning message if global filters are being used. - Add tooltip messages for the checkbox shortcuts. --- apps/opencs/editor.cpp | 47 +++++++++++++++++++++ apps/opencs/editor.hpp | 2 + apps/opencs/model/settings/usersettings.cpp | 4 +- apps/opencs/view/world/tablesubview.cpp | 2 + 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 1d31c8396..51bfc96cc 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include #include @@ -213,6 +216,7 @@ void CS::Editor::createNewFile (const boost::filesystem::path &savePath) mDocumentManager.addDocument (files, savePath, true); mFileDialog.hide(); + showSplashMessage(); } void CS::Editor::createNewGame (const boost::filesystem::path& file) @@ -224,6 +228,7 @@ void CS::Editor::createNewGame (const boost::filesystem::path& file) mDocumentManager.addDocument (files, file, true); mNewGame.hide(); + showSplashMessage(); } void CS::Editor::showStartup() @@ -435,9 +440,51 @@ std::auto_ptr CS::Editor::setupGraphics() void CS::Editor::documentAdded (CSMDoc::Document *document) { mViewManager.addView (document); + showSplashMessage(); } void CS::Editor::lastDocumentDeleted() { QApplication::quit(); } + +void CS::Editor::showSplashMessage() +{ + CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); + if(settings.settingValue ("filter/project-added") == "true" || + settings.settingValue ("filter/project-modified") == "true") + { + QPixmap img(QPixmap(":./openmw-cs.png")); + + QString msgTop("You have active global filters."); + QString msgBottom("Some rows may be hidden!"); + QFont splashFont(QFont("Arial", 16, QFont::Bold)); // TODO: use system font? + //splashFont.setStretch(125); + + QFontMetrics fm(splashFont); + int msgWidth = std::max(fm.width(msgTop), fm.width(msgBottom)); + img.scaledToWidth(msgWidth); + + QSplashScreen *splash = new QSplashScreen(img, Qt::WindowStaysOnTopHint); + splash->setFont(splashFont); + splash->resize(msgWidth+20, splash->height()+fm.lineSpacing()*2+20); // add some margin + + // try to center the message near the center of the saved window + QWidget dummy; + bool xWorkaround = settings.settingValue ("window/x-save-state-workaround").toStdString() == "true"; + if (settings.settingValue ("window/save-state").toStdString() == "true" && + !(xWorkaround && settings.settingValue ("window/maximized").toStdString() == "true")) + { + dummy.restoreGeometry(settings.value("window/geometry").toByteArray()); + splash->move(dummy.x()+std::max(0, (dummy.width()-msgWidth)/2), + dummy.y()+std::max(0, (dummy.height()-splash->height())/2)); + } + else + splash->move(std::max(0, splash->x()-msgWidth/2), splash->y()); + + splash->show(); + splash->showMessage(msgTop+"\n"+msgBottom, Qt::AlignHCenter|Qt::AlignBottom, Qt::red); + QTimer::singleShot(4000, splash, SLOT(close())); // 4 seconds should be enough + splash->raise(); // for X windows + } +} diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 273f0825b..3935ac56a 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -60,6 +60,8 @@ namespace CS boost::filesystem::ofstream mPidFile; bool mFsStrict; + void showSplashMessage(); + void setupDataFiles (const Files::PathContainer& dataDirs); std::pair > readConfig(); diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 5eb795046..f3de34484 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -252,12 +252,12 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() { Setting *projAdded = createSetting (Type_CheckBox, "project-added", "Project::added initial value"); projAdded->setDefaultValue ("false"); - projAdded->setToolTip ("Show records added by the project when a table is first opened." + projAdded->setToolTip ("Show records added by the project when opening a table." " Other records are filterd out."); Setting *projModified = createSetting (Type_CheckBox, "project-modified", "Project::modified initial value"); projModified->setDefaultValue ("false"); - projModified->setToolTip ("Show records modified by the project when a table is first opened." + projModified->setToolTip ("Show records modified by the project when opening a table." " Other records are filterd out."); } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 2d3ec8cc5..40b52709e 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -34,6 +34,8 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D QHBoxLayout *hLayout = new QHBoxLayout; QCheckBox *added = new QCheckBox("A"); QCheckBox *modified = new QCheckBox("M"); + added->setToolTip("Apply filter project::added. Changes to\nthis filter setting is not saved in preferences."); + modified->setToolTip("Apply filter project::modified. Changes to\nthis filter setting is not saved in preferences."); CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); added->setCheckState( userSettings.settingValue ("filter/project-added") == "true" ? Qt::Checked : Qt::Unchecked); From 4b57f6c82153ece3ce0e2f8ca90ef94dc49c5236 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 16 May 2015 08:05:12 +1000 Subject: [PATCH 031/441] Modifying an object instance (e.g. 3D edit) triggers the instances table to scroll to the corresponding record. Should resolve Feature #2554. --- apps/opencs/model/settings/usersettings.cpp | 5 +++++ apps/opencs/view/world/table.cpp | 25 ++++++++++++++++----- apps/opencs/view/world/table.hpp | 4 +++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 41ce593b7..fe9aa9d02 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -214,6 +214,11 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() "Jump to the added or cloned record."); jumpToAdded->setDefaultValue (defaultValue); jumpToAdded->setDeclaredValues (jumpValues); + + Setting *jumpToModified = createSetting (Type_CheckBox, "jump-to-modified", "Jump to modified Record"); + jumpToModified->setDefaultValue ("true"); + jumpToModified->setToolTip ("Whether to jump to the modified record. This setting effects the instances table only." + "\nCan be useful in finding the moved or modified object instance while 3D editing."); } declareSection ("search", "Search & Replace"); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 86daf8af7..c54f2e24b 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -370,10 +370,14 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); - /// \note This signal could instead be connected to a slot that filters out changes not affecting - /// the records status column (for permanence reasons) - connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (tableSizeUpdate())); + if (id == CSMWorld::UniversalId::Type_References) + connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (dataChangedEvent(const QModelIndex&, const QModelIndex&))); + else + /// \note This signal could instead be connected to a slot that filters out changes not affecting + /// the records status column (for permanence reasons) + connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (tableSizeUpdate())); connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)), this, SLOT (selectionSizeUpdate ())); @@ -640,7 +644,7 @@ void CSVWorld::Table::tableSizeUpdate() case CSMWorld::RecordBase::State_BaseOnly: ++size; break; case CSMWorld::RecordBase::State_Modified: ++size; ++modified; break; case CSMWorld::RecordBase::State_ModifiedOnly: ++size; ++modified; break; - case CSMWorld::RecordBase:: State_Deleted: ++deleted; ++modified; break; + case CSMWorld::RecordBase::State_Deleted: ++deleted; ++modified; break; } } } @@ -743,6 +747,7 @@ std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const void CSVWorld::Table::rowsInsertedEvent(const QModelIndex& parent, int start, int end) { tableSizeUpdate(); + if(mJumpToAddedRecord) { selectRow(end); @@ -751,3 +756,13 @@ void CSVWorld::Table::rowsInsertedEvent(const QModelIndex& parent, int start, in clearSelection(); } } + +void CSVWorld::Table::dataChangedEvent(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + tableSizeUpdate(); + + // this event is assumed to be infreqent, don't bother keeping a member variable + CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); + if (settings.setting("table-input/jump-to-modified", "true") == "true") + selectRow(bottomRight.row()); +} diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index becb21f65..f1d87d79a 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -142,7 +142,9 @@ namespace CSVWorld void updateUserSetting (const QString &name, const QStringList &list); - void rowsInsertedEvent(const QModelIndex& parent, int start, int end); + void rowsInsertedEvent(const QModelIndex &parent, int start, int end); + + void dataChangedEvent(const QModelIndex &topLeft, const QModelIndex &bottomRight); }; } From 85a20be3213ae1eda8799ca704dddbb041d7d011 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 30 May 2015 13:34:42 +1000 Subject: [PATCH 032/441] Add a per table subview options section and a checkbox option to scroll the table to the modified record (from another subview). - Always start disabled, needs to be manually set per table - There is no user preference setting, the options are active during the editing session only --- apps/opencs/model/settings/usersettings.cpp | 5 -- apps/opencs/view/world/table.cpp | 26 +++++----- apps/opencs/view/world/table.hpp | 3 ++ apps/opencs/view/world/tablesubview.cpp | 54 +++++++++++++++++++-- apps/opencs/view/world/tablesubview.hpp | 4 ++ 5 files changed, 70 insertions(+), 22 deletions(-) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 3516064bc..ea002c5ed 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -232,11 +232,6 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() "Jump to the added or cloned record."); jumpToAdded->setDefaultValue (defaultValue); jumpToAdded->setDeclaredValues (jumpValues); - - Setting *jumpToModified = createSetting (Type_CheckBox, "jump-to-modified", "Jump to modified Record"); - jumpToModified->setDefaultValue ("true"); - jumpToModified->setToolTip ("Whether to jump to the modified record. This setting effects the instances table only." - "\nCan be useful in finding the moved or modified object instance while 3D editing."); } declareSection ("search", "Search & Replace"); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 7fc8706a6..25162a67f 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -254,7 +254,7 @@ void CSVWorld::Table::mouseDoubleClickEvent (QMouseEvent *event) CSVWorld::Table::Table (const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document) : DragRecordTable(document), mCreateAction (0), - mCloneAction(0),mRecordStatusDisplay (0) + mCloneAction(0),mRecordStatusDisplay (0), mAutoJump (false) { CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); QString jumpSetting = settings.settingValue ("table-input/jump-to-added"); @@ -370,14 +370,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); - if (id == CSMWorld::UniversalId::Type_References) - connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (dataChangedEvent(const QModelIndex&, const QModelIndex&))); - else - /// \note This signal could instead be connected to a slot that filters out changes not affecting - /// the records status column (for permanence reasons) - connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (tableSizeUpdate())); + /// \note This signal could instead be connected to a slot that filters out changes not affecting + /// the records status column (for permanence reasons) + connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (dataChangedEvent(const QModelIndex&, const QModelIndex&))); connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)), this, SLOT (selectionSizeUpdate ())); @@ -765,8 +761,14 @@ void CSVWorld::Table::dataChangedEvent(const QModelIndex &topLeft, const QModelI { tableSizeUpdate(); - // this event is assumed to be infreqent, don't bother keeping a member variable - CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); - if (settings.setting("table-input/jump-to-modified", "true") == "true") + if (mAutoJump) selectRow(bottomRight.row()); } + +void CSVWorld::Table::jumpAfterModChanged(int state) +{ + if(state == Qt::Checked) + mAutoJump = true; + else + mAutoJump = false; +} diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index f1d87d79a..ea6a026f9 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -69,6 +69,7 @@ namespace CSVWorld std::map mDoubleClickActions; bool mJumpToAddedRecord; bool mUnselectAfterJump; + bool mAutoJump; private: @@ -145,6 +146,8 @@ namespace CSVWorld void rowsInsertedEvent(const QModelIndex &parent, int start, int end); void dataChangedEvent(const QModelIndex &topLeft, const QModelIndex &bottomRight); + + void jumpAfterModChanged(int state); }; } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index af0b64447..46393faf8 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -1,6 +1,8 @@ - #include "tablesubview.hpp" +#include +#include +#include #include #include #include @@ -18,7 +20,7 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) -: SubView (id) +: SubView (id), mShowOptions(false), mOptions(0) { QVBoxLayout *layout = new QVBoxLayout; @@ -32,7 +34,37 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D mFilterBox = new CSVFilter::FilterBox (document.getData(), this); - layout->insertWidget (0, mFilterBox); + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->insertWidget(0,mFilterBox); + + mOptions = new QWidget; + + QHBoxLayout *optHLayout = new QHBoxLayout; + QCheckBox *autoJump = new QCheckBox("Auto Jump"); + autoJump->setToolTip ("Whether to jump to the modified record." + "\nCan be useful in finding the moved or modified" + "\nobject instance while 3D editing."); + autoJump->setCheckState(Qt::Unchecked); + connect(autoJump, SIGNAL (stateChanged(int)), mTable, SLOT (jumpAfterModChanged(int))); + optHLayout->insertWidget(0, autoJump); + optHLayout->setContentsMargins (QMargins (0, 3, 0, 0)); + mOptions->setLayout(optHLayout); + mOptions->resize(mOptions->width(), mFilterBox->height()); + mOptions->hide(); + + QPushButton *opt = new QPushButton (); + opt->setIcon (QIcon (":startup/configure")); + opt->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); + opt->setToolTip ("Open additional options for this subview."); + connect (opt, SIGNAL (clicked()), this, SLOT (toggleOptions())); + + QVBoxLayout *buttonLayout = new QVBoxLayout; // work around margin issues + buttonLayout->setContentsMargins (QMargins (0/*left*/, 3/*top*/, 3/*right*/, 0/*bottom*/)); + buttonLayout->insertWidget(0, opt, 0, Qt::AlignVCenter|Qt::AlignRight); + hLayout->insertWidget(1, mOptions); + hLayout->insertLayout(2, buttonLayout); + + layout->insertLayout (0, hLayout); CSVDoc::SizeHintWidget *widget = new CSVDoc::SizeHintWidget; @@ -95,8 +127,7 @@ void CSVWorld::TableSubView::editRequest (const CSMWorld::UniversalId& id, const focusId (id, hint); } -void CSVWorld::TableSubView::updateUserSetting - (const QString &name, const QStringList &list) +void CSVWorld::TableSubView::updateUserSetting (const QString &name, const QStringList &list) { mTable->updateUserSetting(name, list); } @@ -166,3 +197,16 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) return false; } +void CSVWorld::TableSubView::toggleOptions() +{ + if (mShowOptions) + { + mShowOptions = false; + mOptions->hide(); + } + else + { + mShowOptions = true; + mOptions->show(); + } +} diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 9d86c32e4..22ba60889 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -6,6 +6,7 @@ #include class QModelIndex; +class QWidget; namespace CSMWorld { @@ -35,6 +36,8 @@ namespace CSVWorld Table *mTable; TableBottomBox *mBottom; CSVFilter::FilterBox *mFilterBox; + bool mShowOptions; + QWidget *mOptions; public: @@ -63,6 +66,7 @@ namespace CSVWorld void cloneRequest (const CSMWorld::UniversalId& toClone); void createFilterRequest(std::vector< CSMWorld::UniversalId >& types, Qt::DropAction action); + void toggleOptions (); }; } From 445f8280143b6a0286d602b5df43820940700044 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 30 May 2015 18:22:58 +1000 Subject: [PATCH 033/441] Refresh filter only if one exists. Fixes Qt warning message "QSortFilterProxyModel: inconsistent changes reported by source model" when a record is deleted. --- apps/opencs/model/world/idtableproxymodel.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 987d27462..5c621cec1 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -56,6 +56,9 @@ bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex &left, const QModel void CSMWorld::IdTableProxyModel::refreshFilter() { - updateColumnMap(); - invalidateFilter(); + if (mFilter) + { + updateColumnMap(); + invalidateFilter(); + } } From 4152086890f952bfacb144aceefecc343cc20c2d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 30 May 2015 18:24:26 +1000 Subject: [PATCH 034/441] Get the correct index for the type of the record being cloned. --- apps/opencs/model/world/idtable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 04aa271cc..1f1e35b60 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -140,7 +140,7 @@ void CSMWorld::IdTable::cloneRecord(const std::string& origin, const std::string& destination, CSMWorld::UniversalId::Type type) { - int index = mIdCollection->getAppendIndex (destination); + int index = mIdCollection->getAppendIndex (destination, type); beginInsertRows (QModelIndex(), index, index); mIdCollection->cloneRecord(origin, destination, type); From 16bf9716a36b5ada56f8ae105c39d50c0f716906 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 30 May 2015 18:25:43 +1000 Subject: [PATCH 035/441] Scroll the selected row to the middle of the viewport. --- apps/opencs/view/world/table.cpp | 17 +++++++++++++++++ apps/opencs/view/world/table.hpp | 2 ++ 2 files changed, 19 insertions(+) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 25162a67f..692f54b8e 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "../../model/doc/document.hpp" @@ -744,6 +745,10 @@ std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const return idToDrag; } +// parent, start and end depend on the model sending the signal, in this case mProxyModel +// +// If, for example, mModel was used instead, then scrolTo() should use the index +// mProxyModel->mapFromSource(mModel->index(end, 0)) void CSVWorld::Table::rowsInsertedEvent(const QModelIndex& parent, int start, int end) { tableSizeUpdate(); @@ -752,17 +757,29 @@ void CSVWorld::Table::rowsInsertedEvent(const QModelIndex& parent, int start, in { selectRow(end); + // without this delay the scroll works but goes to top for add/clone + QMetaObject::invokeMethod(this, "queuedScrollTo", Qt::QueuedConnection, Q_ARG(int, end)); + if(mUnselectAfterJump) clearSelection(); } } +void CSVWorld::Table::queuedScrollTo(int row) +{ + scrollTo(mProxyModel->index(row, 0), QAbstractItemView::PositionAtCenter); +} + void CSVWorld::Table::dataChangedEvent(const QModelIndex &topLeft, const QModelIndex &bottomRight) { tableSizeUpdate(); if (mAutoJump) + { selectRow(bottomRight.row()); + scrollTo(mProxyModel->index(bottomRight.row(), 0), QAbstractItemView::PositionAtCenter); + //scrollTo(bottomRight, QAbstractItemView::PositionAtCenter); // alternative + } } void CSVWorld::Table::jumpAfterModChanged(int state) diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index ea6a026f9..a3d726a7f 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -148,6 +148,8 @@ namespace CSVWorld void dataChangedEvent(const QModelIndex &topLeft, const QModelIndex &bottomRight); void jumpAfterModChanged(int state); + + void queuedScrollTo(int state); }; } From 193873b82952baba6866051058dc7bbfe23cf341 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 31 May 2015 10:28:45 +1000 Subject: [PATCH 036/441] Feature #1278: Editor: Mouse picking in worldspace widget - With the move to OSG (see: https://forum.openmw.org/viewtopic.php?f=7&t=2755&p=31850#p31839) all the work done so far will soon be removed from the master branch. - The pathgrid editing and integration to the model, as well as other improvements made, are unlikely to be accepted to the master. - These will be maintained here until they can be ported to OSG. It is easier to port when there is a working code base, anyway. --- apps/opencs/view/render/cell.cpp | 7 ++++--- apps/opencs/view/render/mousestate.cpp | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index f7ec24a56..49af585fd 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -120,9 +120,10 @@ bool CSVRender::Cell::addObjects (int start, int end) CSVRender::Cell::Cell (CSMDoc::Document& document, Ogre::SceneManager *sceneManager, const std::string& id, boost::shared_ptr physics, const Ogre::Vector3& origin) - : mDocument (document), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager) - , mPhysics(physics), mX(0), mY(0), mPgIndex(-1), mModel(0), mProxyModel(0) - , mHandler(new CSMWorld::SignalHandler(this)) +: mDocument (document), mId (Misc::StringUtils::lowerCase (id)) +, mProxyModel(0), mModel(0), mPgIndex(-1), mHandler(new CSMWorld::SignalHandler(this)) +, mPhysics(physics), mSceneMgr(sceneManager), mX(0), mY(0) + { mCellNode = sceneManager->getRootSceneNode()->createChildSceneNode(); mCellNode->setPosition (origin); diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index 38dc41fbc..e4921c4b7 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -56,11 +56,11 @@ namespace CSVRender // MouseState::MouseState(WorldspaceWidget *parent) - : mParent(parent), mPhysics(parent->mDocument.getPhysics()), mSceneManager(parent->getSceneManager()) - , mCurrentObj(""), mMouseState(Mouse_Default), mOldCursorPos(0,0), mMouseEventTimer(0) - , mGrabbedSceneNode(""), mGrabbedRefId(""), mOrigObjPos(Ogre::Vector3()) - , mOrigMousePos(Ogre::Vector3()), mOldMousePos(Ogre::Vector3()), mPlane(0) - , mColIndexPosX(0), mColIndexPosY(0), mColIndexPosZ(0), mIdTableModel(0) + : mMouseState(Mouse_Default), mParent(parent), mPhysics(parent->mDocument.getPhysics()) + , mSceneManager(parent->getSceneManager()), mOldCursorPos(0,0), mCurrentObj(""), mGrabbedSceneNode(""), mGrabbedRefId("") + , mMouseEventTimer(0), mPlane(0), mOrigObjPos(Ogre::Vector3()), mOrigMousePos(Ogre::Vector3()) + , mOldMousePos(Ogre::Vector3()), mIdTableModel(0), mColIndexPosX(0) + , mColIndexPosY(0), mColIndexPosZ(0) { const CSMWorld::RefCollection& references = mParent->mDocument.getData().getReferences(); From 610e6591213bb1ce07ed8869f39b0c14c7765646 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 12 Jun 2015 21:38:53 +1000 Subject: [PATCH 037/441] Fix physics model not being moved since commit ec80884. --- apps/opencs/view/render/object.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 3607fb415..9e39ee37b 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -223,12 +223,10 @@ bool CSVRender::Object::referenceDataChanged (const QModelIndex& topLeft, { mReferenceableId = references.getData (index, columnIndex).toString().toUtf8().constData(); - - update(); } + update(); adjust(); - return true; } From ad040462436abea2ef2c62f734707c3d66b8b7d5 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 18 Jun 2015 22:02:08 +1000 Subject: [PATCH 038/441] Mimic vanilla CS behaviour with NPC stats auto calculations. Should resolve Bug #2663. - Currently does not auto-update when race, class or skills tables change. --- apps/opencs/model/world/data.cpp | 8 +- apps/opencs/model/world/data.hpp | 2 + .../model/world/nestedcoladapterimp.cpp | 66 ++- apps/opencs/model/world/refidadapterimp.cpp | 462 ++++++++++++++++-- apps/opencs/model/world/refidadapterimp.hpp | 36 +- apps/opencs/model/world/refidcollection.cpp | 14 +- apps/opencs/model/world/refidcollection.hpp | 4 +- apps/opencs/model/world/usertype.hpp | 44 ++ apps/opencs/view/world/dialoguesubview.cpp | 14 +- apps/opencs/view/world/nestedtable.cpp | 50 +- apps/opencs/view/world/nestedtable.hpp | 3 +- apps/opencs/view/world/util.cpp | 14 +- 12 files changed, 627 insertions(+), 90 deletions(-) create mode 100644 apps/opencs/model/world/usertype.hpp diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 529c8f88f..ffa1ff3c3 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -62,7 +62,8 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0) + mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), + mReferenceables(self()) { int index = 0; @@ -1159,3 +1160,8 @@ void CSMWorld::Data::rowsChanged (const QModelIndex& parent, int start, int end) { emit idListChanged(); } + +const CSMWorld::Data& CSMWorld::Data::self () +{ + return *this; +} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 060e47bd9..f83f9de22 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -121,6 +121,8 @@ namespace CSMWorld static int count (RecordBase::State state, const CollectionBase& collection); + const CSMWorld::Data& self (); + public: Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index b7d09777d..14a436c83 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -6,6 +6,7 @@ #include "idcollection.hpp" #include "pathgrid.hpp" #include "info.hpp" +#include "usertype.hpp" namespace CSMWorld { @@ -1069,23 +1070,66 @@ namespace CSMWorld switch (subColIndex) { case 0: return isInterior; - case 1: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mAmbient : QVariant(QVariant::UserType); - case 2: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mSunlight : QVariant(QVariant::UserType); - case 3: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mFog : QVariant(QVariant::UserType); - case 4: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mFogDensity : QVariant(QVariant::UserType); + case 1: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mAmbient; + else + { + UserInt i(cell.mAmbi.mAmbient); + return QVariant(QVariant::fromValue(i)); + } + } + case 2: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mSunlight; + else + { + UserInt i(cell.mAmbi.mSunlight); + return QVariant(QVariant::fromValue(i)); + } + } + case 3: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mFog; + else + { + UserInt i(cell.mAmbi.mFog); + return QVariant(QVariant::fromValue(i)); + } + } + case 4: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mFogDensity; + else + { + UserFloat f(cell.mAmbi.mFogDensity); + return QVariant(QVariant::fromValue(f)); + } + } case 5: { if (isInterior && !behaveLikeExterior && interiorWater) return cell.mWater; else - return QVariant(QVariant::UserType); + { + UserFloat f(cell.mWater); + return QVariant(QVariant::fromValue(f)); + } + } + case 6: + { + if (isInterior) + { + UserInt i(cell.mMapColor); + return QVariant(QVariant::fromValue(i)); + } + else + return cell.mMapColor; // TODO: how to select? } - case 6: return isInterior ? - QVariant(QVariant::UserType) : cell.mMapColor; // TODO: how to select? //case 7: return isInterior ? //behaveLikeExterior : QVariant(QVariant::UserType); default: throw std::runtime_error("Cell subcolumn index out of range"); diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 4c369ef24..86f0461af 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -5,7 +5,195 @@ #include #include +#include + #include "nestedtablewrapper.hpp" +#include "usertype.hpp" + +namespace +{ + +int is_even(double d) +{ + double int_part; + + modf(d / 2.0, &int_part); + return 2.0 * int_part == d; +} + +int round_ieee_754(double d) +{ + double i = floor(d); + d -= i; + + if(d < 0.5) + return static_cast(i); + if(d > 0.5) + return static_cast(i) + 1; + if(is_even(i)) + return static_cast(i); + return static_cast(i) + 1; +} + +std::vector autoCalculateAttributes (const ESM::NPC &npc, + const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection& skillTable) +{ + // race bonus + bool male = (npc.mFlags & ESM::NPC::Female) == 0; + + if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return std::vector(); + + short level = npc.mNpdt12.mLevel; + + int attr[ESM::Attribute::Length]; + + for (int i = 0; i < ESM::Attribute::Length; ++i) + { + const ESM::Race::MaleFemale& attribute = race.mData.mAttributeValues[i]; + attr[i] = male ? attribute.mMale : attribute.mFemale; + } + + // class bonus + for (int i = 0; i < 2; ++i) + { + int attribute = class_.mData.mAttribute[i]; + if (attribute >= 0 && attribute < ESM::Attribute::Length) + { + attr[attribute] = attr[attribute] + 10; + } + // else log an error + } + + std::vector result(ESM::Attribute::Length); + // skill bonus + for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute) + { + float modifierSum = 0; + + for (int j = 0; j < ESM::Skill::Length; ++j) + { + // if the skill does not exist, throws std::runtime_error ("invalid ID: " + id) + const ESM::Skill& skill = skillTable.getRecord(ESM::Skill::indexToId(j)).get(); + + if (skill.mData.mAttribute != attribute) + continue; + + // is this a minor or major skill? + float add = 0.2f; + for (int k = 0; k < 5; ++k) + { + if (class_.mData.mSkills[k][0] == j) + add = 0.5; + } + for (int k = 0; k < 5; ++k) + { + if (class_.mData.mSkills[k][1] == j) + add = 1.0; + } + modifierSum += add; + } + result[attribute] = std::min(round_ieee_754(attr[attribute] + (level-1) * modifierSum), 100); + } + + return result; +} + +std::vector autoCalculateSkills (const ESM::NPC &npc, + const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection& skillTable) +{ + unsigned char skills[ESM::Skill::Length]; + for (int i = 0; i < ESM::Skill::Length; ++i) + skills[i] = 0; + + for (int i = 0; i < 2; ++i) + { + int bonus = (i == 0) ? 10 : 25; + + for (int i2 = 0; i2 < 5; ++i2) + { + int index = class_.mData.mSkills[i2][i]; + if (index >= 0 && index < ESM::Skill::Length) + { + skills[index] = bonus; + } + } + } + + std::vector result(ESM::Skill::Length); + for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) + { + float majorMultiplier = 0.1f; + float specMultiplier = 0.0f; + + int raceBonus = 0; + int specBonus = 0; + + for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) + { + if (race.mData.mBonus[raceSkillIndex].mSkill == skillIndex) + { + raceBonus = race.mData.mBonus[raceSkillIndex].mBonus; + break; + } + } + + for (int k = 0; k < 5; ++k) + { + // is this a minor or major skill? + if ((class_.mData.mSkills[k][0] == skillIndex) || (class_.mData.mSkills[k][1] == skillIndex)) + { + majorMultiplier = 1.0f; + break; + } + } + + // is this skill in the same Specialization as the class? + const ESM::Skill& skill = skillTable.getRecord(ESM::Skill::indexToId(skillIndex)).get(); + if (skill.mData.mSpecialization == class_.mData.mSpecialization) + { + specMultiplier = 0.5f; + specBonus = 5; + } + + // Must gracefully handle level 0 + result[skillIndex] = std::min(round_ieee_754(skills[skillIndex] + 5 + raceBonus + specBonus + + (npc.mNpdt12.mLevel-1) * (majorMultiplier + specMultiplier)), 100); + } + + return result; +} + +unsigned short autoCalculateHealth(const ESM::NPC &npc, + const ESM::Class& class_, const std::vector& attr) +{ + int multiplier = 3; + + if (class_.mData.mSpecialization == ESM::Class::Combat) + multiplier += 2; + else if (class_.mData.mSpecialization == ESM::Class::Stealth) + multiplier += 1; + + if (class_.mData.mAttribute[0] == ESM::Attribute::Endurance + || class_.mData.mAttribute[1] == ESM::Attribute::Endurance) + multiplier += 1; + + return floor(0.5f * (attr[ESM::Attribute::Strength]+ attr[ESM::Attribute::Endurance])) + + multiplier * (npc.mNpdt12.mLevel-1); +} + +unsigned short autoCalculateMana(const std::vector& attr) +{ + return attr[ESM::Attribute::Intelligence] * 2; +} + +unsigned short autoCalculateFatigue(const std::vector& attr) +{ + return attr[ESM::Attribute::Strength] + attr[ESM::Attribute::Willpower] + + attr[ESM::Attribute::Agility] + attr[ESM::Attribute::Endurance]; +} + +} CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) : InventoryColumns (columns) {} @@ -553,8 +741,12 @@ CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) mMisc(NULL) {} -CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) -: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns) +CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& skills) +: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns), + mRaceTable(races), mClassTable(classes), mSkillTable(skills) {} QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) @@ -629,8 +821,46 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d npc.mFlags &= ~iter->second; if (iter->second == ESM::NPC::Autocalc) - npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS - : ESM::NPC::NPC_DEFAULT; + { + if(value.toInt() != 0) + { + npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; + + // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) + const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); + const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); + std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); + if (attr.empty()) + return; + + std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); + + ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; + + npcStruct.mLevel = npc.mNpdt12.mLevel; + npcStruct.mStrength = attr[ESM::Attribute::Strength]; + npcStruct.mIntelligence = attr[ESM::Attribute::Intelligence]; + npcStruct.mWillpower = attr[ESM::Attribute::Willpower]; + npcStruct.mAgility = attr[ESM::Attribute::Agility]; + npcStruct.mSpeed = attr[ESM::Attribute::Speed]; + npcStruct.mEndurance = attr[ESM::Attribute::Endurance]; + npcStruct.mPersonality = attr[ESM::Attribute::Personality]; + npcStruct.mLuck = attr[ESM::Attribute::Luck]; + for (int i = 0; i < ESM::Skill::Length; ++i) + { + npcStruct.mSkills[i] = skills[i]; + } + npcStruct.mHealth = autoCalculateHealth(npc, class_, attr); + npcStruct.mMana = autoCalculateMana(attr); + npcStruct.mFatigue = autoCalculateFatigue(attr); + npcStruct.mDisposition = npc.mNpdt12.mDisposition; + npcStruct.mReputation = npc.mNpdt12.mReputation; + npcStruct.mRank = npc.mNpdt12.mRank; + npcStruct.mGold = npc.mNpdt12.mGold; + } + else + npc.mNpdtType = ESM::NPC::NPC_DEFAULT; + } } else { @@ -643,7 +873,9 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d record.setModified (npc); } -CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter () +CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter(const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& classes, const CSMWorld::IdCollection& skills) + : mRaceTable(races), mClassTable(classes), mSkillTable(skills) {} void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, @@ -691,7 +923,8 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); - const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; + const ESM::NPC npc = record.get(); + const ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; if (subColIndex == 0) switch (subRowIndex) @@ -707,18 +940,43 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * default: return QVariant(); // throw an exception here? } else if (subColIndex == 1) - switch (subRowIndex) + // It may be possible to have mNpdt52 values different to autocalculated ones when + // first loaded, so re-calculate + if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - case 0: return static_cast(npcStruct.mStrength); - case 1: return static_cast(npcStruct.mIntelligence); - case 2: return static_cast(npcStruct.mWillpower); - case 3: return static_cast(npcStruct.mAgility); - case 4: return static_cast(npcStruct.mSpeed); - case 5: return static_cast(npcStruct.mEndurance); - case 6: return static_cast(npcStruct.mPersonality); - case 7: return static_cast(npcStruct.mLuck); - default: return QVariant(); // throw an exception here? + const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); + const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); + std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); + + if (attr.empty()) + return QVariant(); + + switch (subRowIndex) + { + case 0: return static_cast(attr[ESM::Attribute::Strength]); + case 1: return static_cast(attr[ESM::Attribute::Intelligence]); + case 2: return static_cast(attr[ESM::Attribute::Willpower]); + case 3: return static_cast(attr[ESM::Attribute::Agility]); + case 4: return static_cast(attr[ESM::Attribute::Speed]); + case 5: return static_cast(attr[ESM::Attribute::Endurance]); + case 6: return static_cast(attr[ESM::Attribute::Personality]); + case 7: return static_cast(attr[ESM::Attribute::Luck]); + default: return QVariant(); // throw an exception here? + } } + else + switch (subRowIndex) + { + case 0: return static_cast(npcStruct.mStrength); + case 1: return static_cast(npcStruct.mIntelligence); + case 2: return static_cast(npcStruct.mWillpower); + case 3: return static_cast(npcStruct.mAgility); + case 4: return static_cast(npcStruct.mSpeed); + case 5: return static_cast(npcStruct.mEndurance); + case 6: return static_cast(npcStruct.mPersonality); + case 7: return static_cast(npcStruct.mLuck); + default: return QVariant(); // throw an exception here? + } else return QVariant(); // throw an exception here? } @@ -761,7 +1019,10 @@ int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *c return 8; } -CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter () +CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter(const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& skills) + : mRaceTable(races), mClassTable(classes), mSkillTable(skills) {} void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow (const RefIdColumn *column, @@ -809,7 +1070,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); - const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; + const ESM::NPC npc = record.get(); if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length) throw std::runtime_error ("index out of range"); @@ -817,7 +1078,26 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu if (subColIndex == 0) return QString(ESM::Skill::sSkillNames[subRowIndex].c_str()); else if (subColIndex == 1) - return static_cast(npcStruct.mSkills[subRowIndex]); + { + // It may be possible to have mNpdt52 values different to autocalculated ones when + // first loaded, so re-calculate + if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + { + // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) + const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); + const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); + std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); + + int value = static_cast(skills[subRowIndex]); + + return static_cast(skills[subRowIndex]); + } + else + { + const ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; + return static_cast(npcStruct.mSkills[subRowIndex]); + } + } else return QVariant(); // throw an exception here? } @@ -852,7 +1132,10 @@ int CSMWorld::NpcSkillsRefIdAdapter::getNestedRowsCount(const RefIdColumn *colum return ESM::Skill::Length; } -CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter () +CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter(const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& skills) + : mRaceTable(races), mClassTable(classes), mSkillTable(skills) {} CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter() @@ -888,16 +1171,45 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); - bool autoCalc = (record.get().mFlags & ESM::NPC::Autocalc) != 0; + const ESM::NPC npc = record.get(); + bool autoCalc = (npc.mFlags & ESM::NPC::Autocalc) != 0; + + // It may be possible to have mNpdt52 values different to autocalculated ones when + // first loaded, so re-calculate if (autoCalc) + { + // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) + const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); + const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); + std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); + + if (attr.empty()) + return QVariant(); + switch (subColIndex) { - case 0: return static_cast(record.get().mNpdt12.mLevel); - case 1: return QVariant(QVariant::UserType); - case 2: return QVariant(QVariant::UserType); - case 3: return QVariant(QVariant::UserType); - case 4: return QVariant(QVariant::UserType); + case 0: return static_cast(npc.mNpdt12.mLevel); + case 1: + { + UserInt i(0); // unknown + return QVariant(QVariant::fromValue(i)); + } + case 2: + { + UserInt i(autoCalculateHealth(npc, class_, attr)); + return QVariant(QVariant::fromValue(i)); + } + case 3: + { + UserInt i(autoCalculateMana(attr)); + return QVariant(QVariant::fromValue(i)); + } + case 4: + { + UserInt i(autoCalculateFatigue(attr)); + return QVariant(QVariant::fromValue(i)); + } case 5: return static_cast(record.get().mNpdt12.mDisposition); case 6: return static_cast(record.get().mNpdt12.mReputation); case 7: return static_cast(record.get().mNpdt12.mRank); @@ -905,6 +1217,7 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column case 9: return record.get().mPersistent == true; default: return QVariant(); // throw an exception here? } + } else switch (subColIndex) { @@ -934,30 +1247,107 @@ void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column, if (autoCalc) switch(subColIndex) { - case 0: npc.mNpdt12.mLevel = static_cast(value.toInt()); break; + case 0: + { + npc.mNpdt12.mLevel = static_cast(value.toInt()); break; + + const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); + const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); + std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); + if (attr.empty()) + return; + + ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; + + std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); + + npcStruct.mLevel = npc.mNpdt12.mLevel; + npcStruct.mStrength = attr[ESM::Attribute::Strength]; + npcStruct.mIntelligence = attr[ESM::Attribute::Intelligence]; + npcStruct.mWillpower = attr[ESM::Attribute::Willpower]; + npcStruct.mAgility = attr[ESM::Attribute::Agility]; + npcStruct.mSpeed = attr[ESM::Attribute::Speed]; + npcStruct.mEndurance = attr[ESM::Attribute::Endurance]; + npcStruct.mPersonality = attr[ESM::Attribute::Personality]; + npcStruct.mLuck = attr[ESM::Attribute::Luck]; + for (int i = 0; i < ESM::Skill::Length; ++i) + { + npcStruct.mSkills[i] = skills[i]; + } + npcStruct.mHealth = autoCalculateHealth(npc, class_, attr); + npcStruct.mMana = autoCalculateMana(attr); + npcStruct.mFatigue = autoCalculateFatigue(attr); + + break; + } case 1: return; case 2: return; case 3: return; case 4: return; - case 5: npc.mNpdt12.mDisposition = static_cast(value.toInt()); break; - case 6: npc.mNpdt12.mReputation = static_cast(value.toInt()); break; - case 7: npc.mNpdt12.mRank = static_cast(value.toInt()); break; - case 8: npc.mNpdt12.mGold = value.toInt(); break; + case 5: + { + npc.mNpdt12.mDisposition = static_cast(value.toInt()); + npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition; + break; + } + case 6: + { + npc.mNpdt12.mReputation = static_cast(value.toInt()); + npc.mNpdt52.mReputation = npc.mNpdt12.mReputation; + break; + } + case 7: + { + npc.mNpdt12.mRank = static_cast(value.toInt()); + npc.mNpdt52.mRank = npc.mNpdt12.mRank; + break; + } + case 8: + { + npc.mNpdt12.mGold = value.toInt(); + npc.mNpdt52.mGold = npc.mNpdt12.mGold; + break; + } case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } else switch(subColIndex) { - case 0: npc.mNpdt52.mLevel = static_cast(value.toInt()); break; + case 0: + { + npc.mNpdt52.mLevel = static_cast(value.toInt()); + npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; + break; + } case 1: npc.mNpdt52.mFactionID = static_cast(value.toInt()); break; case 2: npc.mNpdt52.mHealth = static_cast(value.toInt()); break; case 3: npc.mNpdt52.mMana = static_cast(value.toInt()); break; case 4: npc.mNpdt52.mFatigue = static_cast(value.toInt()); break; - case 5: npc.mNpdt52.mDisposition = static_cast(value.toInt()); break; - case 6: npc.mNpdt52.mReputation = static_cast(value.toInt()); break; - case 7: npc.mNpdt52.mRank = static_cast(value.toInt()); break; - case 8: npc.mNpdt52.mGold = value.toInt(); break; + case 5: + { + npc.mNpdt52.mDisposition = static_cast(value.toInt()); + npc.mNpdt12.mDisposition = npc.mNpdt52.mDisposition; + break; + } + case 6: + { + npc.mNpdt52.mReputation = static_cast(value.toInt()); + npc.mNpdt12.mReputation = npc.mNpdt52.mReputation; + break; + } + case 7: + { + npc.mNpdt52.mRank = static_cast(value.toInt()); + npc.mNpdt12.mRank = npc.mNpdt52.mRank; + break; + } + case 8: + { + npc.mNpdt52.mGold = value.toInt(); + npc.mNpdt12.mGold = npc.mNpdt52.mGold; + break; + } case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 869996da5..b12f84d5e 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -11,12 +11,16 @@ #include #include #include +#include +#include +#include #include "record.hpp" #include "refiddata.hpp" #include "universalid.hpp" #include "refidadapter.hpp" #include "nestedtablewrapper.hpp" +#include "idcollection.hpp" namespace CSMWorld { @@ -802,10 +806,16 @@ namespace CSMWorld class NpcRefIdAdapter : public ActorRefIdAdapter { NpcColumns mColumns; + const IdCollection& mRaceTable; + const IdCollection& mClassTable; + const IdCollection& mSkillTable; public: - NpcRefIdAdapter (const NpcColumns& columns); + NpcRefIdAdapter (const NpcColumns& columns, + const IdCollection& races, + const IdCollection& classes, + const IdCollection& skills); virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const; @@ -850,9 +860,15 @@ namespace CSMWorld class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase { + const IdCollection& mRaceTable; + const IdCollection& mClassTable; + const IdCollection& mSkillTable; + public: - NpcAttributesRefIdAdapter (); + NpcAttributesRefIdAdapter (const IdCollection& races, + const IdCollection& classes, + const IdCollection& skills); virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const; @@ -879,9 +895,15 @@ namespace CSMWorld class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase { + const IdCollection& mRaceTable; + const IdCollection& mClassTable; + const IdCollection& mSkillTable; + public: - NpcSkillsRefIdAdapter (); + NpcSkillsRefIdAdapter (const IdCollection& races, + const IdCollection& classes, + const IdCollection& skills); virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const; @@ -908,12 +930,18 @@ namespace CSMWorld class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase { + const IdCollection& mRaceTable; + const IdCollection& mClassTable; + const IdCollection& mSkillTable; + NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&); NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&); public: - NpcMiscRefIdAdapter (); + NpcMiscRefIdAdapter (const IdCollection& races, + const IdCollection& classes, + const IdCollection& skills); virtual ~NpcMiscRefIdAdapter(); virtual void addNestedRow (const RefIdColumn *column, diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 5495926b4..739e9cec4 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -10,6 +10,7 @@ #include "columns.hpp" #include "nestedtablewrapper.hpp" #include "nestedcoladapterimp.hpp" +#include "data.hpp" CSMWorld::RefIdColumn::RefIdColumn (int columnId, Display displayType, int flag, bool editable, bool userEditable) @@ -36,7 +37,7 @@ const CSMWorld::RefIdAdapter& CSMWorld::RefIdCollection::findAdapter (UniversalI return *iter->second; } -CSMWorld::RefIdCollection::RefIdCollection() +CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) { BaseColumns baseColumns; @@ -437,7 +438,7 @@ CSMWorld::RefIdCollection::RefIdCollection() ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); npcColumns.mAttributes = &mColumns.back(); std::map attrMap; - attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter())); + attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attrMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcAttributes, CSMWorld::ColumnBase::Display_String, false, false)); @@ -449,7 +450,7 @@ CSMWorld::RefIdCollection::RefIdCollection() ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); npcColumns.mSkills = &mColumns.back(); std::map skillsMap; - skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter())); + skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcSkills, CSMWorld::ColumnBase::Display_String, false, false)); @@ -461,10 +462,11 @@ CSMWorld::RefIdCollection::RefIdCollection() ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); npcColumns.mMisc = &mColumns.back(); std::map miscMap; - miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter())); + miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), miscMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer, + ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcFactionID, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( @@ -609,7 +611,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous, new MiscRefIdAdapter (inventoryColumns, key))); mAdapters.insert (std::make_pair (UniversalId::Type_Npc, - new NpcRefIdAdapter (npcColumns))); + new NpcRefIdAdapter (npcColumns, data.getRaces(), data.getClasses(), data.getSkills()))); mAdapters.insert (std::make_pair (UniversalId::Type_Probe, new ToolRefIdAdapter (UniversalId::Type_Probe, toolsColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Repair, diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index 4d511d12d..e8e633663 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -20,6 +20,7 @@ namespace CSMWorld class RefIdAdapter; struct NestedTableWrapperBase; class NestedRefIdAdapterBase; + class Data; class RefIdColumn : public NestableColumn { @@ -56,7 +57,8 @@ namespace CSMWorld public: - RefIdCollection(); + // race, classes and skills required for NPC autocalc + RefIdCollection(const Data& data); virtual ~RefIdCollection(); diff --git a/apps/opencs/model/world/usertype.hpp b/apps/opencs/model/world/usertype.hpp new file mode 100644 index 000000000..e0b3c2e2f --- /dev/null +++ b/apps/opencs/model/world/usertype.hpp @@ -0,0 +1,44 @@ +#ifndef CSM_WORLD_USERTYPE_H +#define CSM_WORLD_USERTYPE_H + +#include +#include + +namespace CSMWorld +{ + // Idea from ksimons @stackoverflow + class UserInt + { + public: + + UserInt() : mValue(0) { } + UserInt(int value) : mValue(value) { } + UserInt(const UserInt &other) { mValue = other.mValue; } + ~UserInt() { } + int value() const { return mValue; } + + private: + + int mValue; + }; + + class UserFloat + { + public: + + UserFloat() : mValue(0) { } + UserFloat(float value) : mValue(value) { } + UserFloat(const UserFloat &other) { mValue = other.mValue; } + ~UserFloat() { } + float value() const { return mValue; } + + private: + + float mValue; + }; +} + +Q_DECLARE_METATYPE(CSMWorld::UserInt); +Q_DECLARE_METATYPE(CSMWorld::UserFloat); + +#endif // CSM_WORLD_USERTYPE_H diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 66e8fcb7a..3c35b095d 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -468,17 +468,19 @@ void CSVWorld::EditWidget::remake(int row) static_cast (mTable->data (mTable->index (row, typeColumn)).toInt()), mTable->data (mTable->index (row, idColumn)).toString().toUtf8().constData()); - NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this); - // FIXME: does not work well when enum delegates are used - //table->resizeColumnsToContents(); - - if(mTable->index(row, i).data().type() == QVariant::UserType) + bool editable = mTable->index(row, i).data().type() != QVariant::UserType; + NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this, editable); + if (!editable) { table->setEditTriggers(QAbstractItemView::NoEditTriggers); - table->setEnabled(false); + table->setSelectionMode(QAbstractItemView::NoSelection); + table->setStyleSheet("QTableView { color: gray; }"); + table->horizontalHeader()->setStyleSheet("QHeaderView { color: gray; }"); } else table->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::CurrentChanged); + // FIXME: does not work well when enum delegates are used + //table->resizeColumnsToContents(); int rows = mTable->rowCount(mTable->index(row, i)); int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 112873cb9..7f5658b88 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -13,13 +13,14 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, - QWidget* parent) + QWidget* parent, + bool editable) : QTableView(parent), + mAddNewRowAction(0), + mRemoveRowAction(0), mUndoStack(document.getUndoStack()), mModel(model) { - mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); - setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); @@ -32,32 +33,36 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, int columns = model->columnCount(QModelIndex()); - for(int i = 0 ; i < columns; ++i) - { - CSMWorld::ColumnBase::Display display = static_cast ( - model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + setModel(model); - CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display, - mDispatcher, - document, - this); + setAcceptDrops(true); - setItemDelegateForColumn(i, delegate); - } + if (editable) + { + mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); + for(int i = 0 ; i < columns; ++i) + { + CSMWorld::ColumnBase::Display display = static_cast ( + model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - setModel(model); + CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display, + mDispatcher, + document, + this); - setAcceptDrops(true); + setItemDelegateForColumn(i, delegate); + } - mAddNewRowAction = new QAction (tr ("Add new row"), this); + mAddNewRowAction = new QAction (tr ("Add new row"), this); - connect(mAddNewRowAction, SIGNAL(triggered()), - this, SLOT(addNewRowActionTriggered())); + connect(mAddNewRowAction, SIGNAL(triggered()), + this, SLOT(addNewRowActionTriggered())); - mRemoveRowAction = new QAction (tr ("Remove row"), this); + mRemoveRowAction = new QAction (tr ("Remove row"), this); - connect(mRemoveRowAction, SIGNAL(triggered()), - this, SLOT(removeRowActionTriggered())); + connect(mRemoveRowAction, SIGNAL(triggered()), + this, SLOT(removeRowActionTriggered())); + } } void CSVWorld::NestedTable::dragEnterEvent(QDragEnterEvent *event) @@ -70,6 +75,9 @@ void CSVWorld::NestedTable::dragMoveEvent(QDragMoveEvent *event) void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) { + if (!mRemoveRowAction || !mAddNewRowAction) + return; + QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu(this); diff --git a/apps/opencs/view/world/nestedtable.hpp b/apps/opencs/view/world/nestedtable.hpp index 5db977942..30af6b211 100644 --- a/apps/opencs/view/world/nestedtable.hpp +++ b/apps/opencs/view/world/nestedtable.hpp @@ -36,7 +36,8 @@ namespace CSVWorld NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, - QWidget* parent = NULL); + QWidget* parent = NULL, + bool editable = true); protected: void dragEnterEvent(QDragEnterEvent *event); diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index e1d165a24..0ecd7779f 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -17,6 +17,7 @@ #include "../../model/world/commands.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/usertype.hpp" #include "dialoguespinbox.hpp" #include "scriptedit.hpp" @@ -153,7 +154,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO const QModelIndex& index) const { CSMWorld::ColumnBase::Display display = getDisplayTypeFromIndex(index); - + // This createEditor() method is called implicitly from tables. // For boolean values in tables use the default editor (combobox). // Checkboxes is looking ugly in the table view. @@ -295,8 +296,15 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde if (!n.isEmpty()) { if (!v.isValid()) - v = QVariant(editor->property(n).userType(), (const void *)0); - editor->setProperty(n, v); + editor->setProperty(n, QVariant(editor->property(n).userType(), (const void *)0)); + else if (v.type() == QVariant::UserType + && QString(v.typeName()) == "CSMWorld::UserFloat" && v.canConvert()) + editor->setProperty(n, QVariant(v.value().value())); + else if (v.type() == QVariant::UserType + && QString(v.typeName()) == "CSMWorld::UserInt" && v.canConvert()) + editor->setProperty(n, QVariant(v.value().value())); + else + editor->setProperty(n, v); } } From 6b00d4ad9155d67ddf3c221b9d1b810e6ff30a86 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 24 Jun 2015 21:05:59 +1000 Subject: [PATCH 039/441] Move NPC autocalc to a separate component so that it can be shared between OpenMW and OpenCS. - Vanilla behaviour mimic'd where possible, except copying over autocalc spells to the npc's spell list when the status changes --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/columns.cpp | 4 + apps/opencs/model/world/columns.hpp | 4 + apps/opencs/model/world/data.cpp | 504 +++++++++++++- apps/opencs/model/world/data.hpp | 31 +- apps/opencs/model/world/idtree.cpp | 5 + apps/opencs/model/world/idtree.hpp | 12 +- apps/opencs/model/world/refidadapterimp.cpp | 638 ++++++++---------- apps/opencs/model/world/refidadapterimp.hpp | 119 +--- apps/opencs/model/world/refidcollection.cpp | 21 +- apps/opencs/view/world/dialoguesubview.cpp | 32 + apps/opencs/view/world/dialoguesubview.hpp | 2 + apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwclass/npc.cpp | 199 +----- apps/openmw/mwgui/levelupdialog.cpp | 4 +- .../mwmechanics/mechanicsmanagerimp.cpp | 10 +- apps/openmw/mwworld/esmstore.cpp | 4 +- apps/openmw/mwworld/esmstore.hpp | 6 +- apps/openmw/mwworld/store.hpp | 96 +-- components/CMakeLists.txt | 24 +- components/gameplay/autocalc.cpp | 207 ++++++ components/gameplay/autocalc.hpp | 47 ++ components/gameplay/autocalcspell.cpp | 240 +++++++ components/gameplay/autocalcspell.hpp | 38 ++ components/gameplay/store.hpp | 138 ++++ 25 files changed, 1631 insertions(+), 760 deletions(-) create mode 100644 components/gameplay/autocalc.cpp create mode 100644 components/gameplay/autocalc.hpp create mode 100644 components/gameplay/autocalcspell.cpp create mode 100644 components/gameplay/autocalcspell.hpp create mode 100644 components/gameplay/store.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d49541590..4e7c4123a 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -26,7 +26,7 @@ opencs_units_noqt (model/world universalid record commands columnbase scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection - idcompletionmanager + idcompletionmanager npcstats ) opencs_hdrs_noqt (model/world diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 9491c3246..f3f78d2c6 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -311,6 +311,10 @@ namespace CSMWorld { ColumnId_WaterLevel, "Water Level" }, { ColumnId_MapColor, "Map Color" }, + { ColumnId_SpellSrc, "From Race" }, + { ColumnId_SpellCost, "Cast Cost" }, + { ColumnId_SpellChance, "Cast Chance" }, + { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue3, "Use value 3" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 191bbdea8..456de27ec 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -302,6 +302,10 @@ namespace CSMWorld ColumnId_WaterLevel = 273, ColumnId_MapColor = 274, + ColumnId_SpellSrc = 275, + ColumnId_SpellCost = 276, + ColumnId_SpellChance = 277, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ffa1ff3c3..4e393fbc3 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -11,6 +11,10 @@ #include #include +#include +#include +#include + #include "idtable.hpp" #include "idtree.hpp" #include "columnimp.hpp" @@ -19,6 +23,117 @@ #include "resourcesmanager.hpp" #include "resourcetable.hpp" #include "nestedcoladapterimp.hpp" +#include "npcstats.hpp" + +namespace +{ + class SpellStore : public GamePlay::CommonStore + { + const CSMWorld::NestedIdCollection& mSpells; + std::vector mLocal; + + public: + + SpellStore(const CSMWorld::NestedIdCollection& spells) + : mSpells(spells) + { + // prepare data in a format used by OpenMW store + for (int index = 0; index < mSpells.getSize(); ++index) + { + ESM::Spell *spell = const_cast(&mSpells.getRecord(index).get()); + mLocal.push_back(spell); + } + } + + ~SpellStore() {} + + typedef GamePlay::SharedIterator iterator; + + virtual iterator begin() const + { + return mLocal.begin(); + } + + virtual iterator end() const + { + return mLocal.end(); + } + + virtual const ESM::Spell *find(const std::string &id) const + { + return &mSpells.getRecord(id).get(); + } + + virtual size_t getSize() const + { + return mSpells.getSize(); + } + + private: + // not used in OpenCS + virtual void load(ESM::ESMReader &esm, const std::string &id) + { + } + }; + + class CSStore : public GamePlay::StoreWrap + { + const CSMWorld::IdCollection& mGmstTable; + const CSMWorld::IdCollection& mSkillTable; + const CSMWorld::IdCollection& mMagicEffectTable; + const SpellStore mSpellStore; + + public: + + CSStore(const CSMWorld::IdCollection& gmst, + const CSMWorld::IdCollection& skills, + const CSMWorld::IdCollection& magicEffects, + const CSMWorld::NestedIdCollection& spells) + : mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpellStore(spells) + { } + ~CSStore() {} + + virtual int findGmstInt(const std::string& name) const + { + return mGmstTable.getRecord(name).get().getInt(); + } + + virtual float findGmstFloat(const std::string& name) const + { + return mGmstTable.getRecord(name).get().getFloat(); + } + + virtual const ESM::Skill *findSkill(int index) const + { + // if the skill does not exist, throws std::runtime_error ("invalid ID: " + id) + return &mSkillTable.getRecord(ESM::Skill::indexToId(index)).get(); + } + + virtual const ESM::MagicEffect* findMagicEffect(int id) const + { + // if the magic effect does not exist, throws std::runtime_error ("invalid ID: " + id) + return &mMagicEffectTable.getRecord(ESM::MagicEffect::indexToId((short)id)).get(); + } + + virtual const GamePlay::CommonStore& getSpells() const + { + return mSpellStore; + } + }; + + unsigned short autoCalculateMana(GamePlay::StatsBase& stats) + { + return stats.getBaseAttribute(ESM::Attribute::Intelligence) * 2; + } + + unsigned short autoCalculateFatigue(GamePlay::StatsBase& stats) + { + return stats.getBaseAttribute(ESM::Attribute::Strength) + + stats.getBaseAttribute(ESM::Attribute::Willpower) + + stats.getBaseAttribute(ESM::Attribute::Agility) + + stats.getBaseAttribute(ESM::Attribute::Endurance); + } +} void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update) { @@ -203,7 +318,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.addColumn (new RecordStateColumn); mSpells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Spell)); mSpells.addColumn (new NameColumn); - mSpells.addColumn (new SpellTypeColumn); + mSpells.addColumn (new SpellTypeColumn); // ColumnId_SpellType mSpells.addColumn (new CostColumn); mSpells.addColumn (new FlagColumn (Columns::ColumnId_AutoCalc, 0x1)); mSpells.addColumn (new FlagColumn (Columns::ColumnId_StarterSpell, 0x2)); @@ -517,11 +632,40 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Videos)), UniversalId::Type_Video); + // for autocalc updates when gmst/race/class/skils tables change + CSMWorld::IdTable *gmsts = + static_cast(getTableModel(UniversalId::Type_Gmst)); + CSMWorld::IdTable *skills = + static_cast(getTableModel(UniversalId::Type_Skill)); + CSMWorld::IdTable *classes = + static_cast(getTableModel(UniversalId::Type_Class)); + CSMWorld::IdTree *races = + static_cast(getTableModel(UniversalId::Type_Race)); + CSMWorld::IdTree *objects = + static_cast(getTableModel(UniversalId::Type_Referenceable)); + + connect (gmsts, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (gmstDataChanged (const QModelIndex&, const QModelIndex&))); + connect (skills, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (skillDataChanged (const QModelIndex&, const QModelIndex&))); + connect (classes, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (classDataChanged (const QModelIndex&, const QModelIndex&))); + connect (races, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (raceDataChanged (const QModelIndex&, const QModelIndex&))); + connect (objects, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (npcDataChanged (const QModelIndex&, const QModelIndex&))); + connect (this, SIGNAL (updateNpcAutocalc (int, const std::string&)), + objects, SLOT (updateNpcAutocalc (int, const std::string&))); + connect (this, SIGNAL (cacheNpcStats (const std::string&, NpcStats*)), + this, SLOT (cacheNpcStatsEvent (const std::string&, NpcStats*))); + mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files } CSMWorld::Data::~Data() { + clearNpcStatsCache(); + for (std::vector::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter) delete *iter; @@ -1165,3 +1309,361 @@ const CSMWorld::Data& CSMWorld::Data::self () { return *this; } + +void CSMWorld::Data::skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // mData.mAttribute (affects attributes skill bonus autocalc) + // mData.mSpecialization (affects skills autocalc) + CSMWorld::IdTable *skillModel = + static_cast(getTableModel(CSMWorld::UniversalId::Type_Skill)); + + int attributeColumn = skillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute); + int specialisationColumn = skillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation); + + if ((topLeft.column() <= attributeColumn && attributeColumn <= bottomRight.column()) + || (topLeft.column() <= specialisationColumn && specialisationColumn <= bottomRight.column())) + { + clearNpcStatsCache(); + + std::string empty; + emit updateNpcAutocalc(0/*all*/, empty); + } +} + +void CSMWorld::Data::classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // update autocalculated attributes/skills of every NPC with matching class + // - mData.mAttribute[2] + // - mData.mSkills[5][2] + // - mData.mSpecialization + CSMWorld::IdTable *classModel = + static_cast(getTableModel(CSMWorld::UniversalId::Type_Class)); + + int attribute1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute1); // +1 + int majorSkill1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_MajorSkill1); // +4 + int minorSkill1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_MinorSkill1); // +4 + int specialisationColumn = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation); + + if ((topLeft.column() > attribute1Column+1 || attribute1Column > bottomRight.column()) + && (topLeft.column() > majorSkill1Column+4 || majorSkill1Column > bottomRight.column()) + && (topLeft.column() > minorSkill1Column+4 || minorSkill1Column > bottomRight.column()) + && (topLeft.column() > specialisationColumn || specialisationColumn > bottomRight.column())) + { + return; + } + + // get the affected class + int idColumn = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + for (int classRow = topLeft.row(); classRow <= bottomRight.row(); ++classRow) + { + clearNpcStatsCache(); + + std::string classId = + classModel->data(classModel->index(classRow, idColumn)).toString().toUtf8().constData(); + emit updateNpcAutocalc(1/*class*/, classId); + } +} + +void CSMWorld::Data::raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // affects racial bonus attributes & skills + // - mData.mAttributeValues[] + // - mData.mBonus[].mBonus + CSMWorld::IdTree *raceModel = + static_cast(getTableModel(CSMWorld::UniversalId::Type_Race)); + + int attrColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceAttributes); + int bonusColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceSkillBonus); + + bool match = false; + if (topLeft.parent().isValid() && bottomRight.parent().isValid()) + { + if ((topLeft.parent().column() <= attrColumn && attrColumn <= bottomRight.parent().column()) + || (topLeft.parent().column() <= bonusColumn && bonusColumn <= bottomRight.parent().column())) + { + match = true; // TODO: check for specific nested column? + } + } + else + { + if ((topLeft.column() <= attrColumn && attrColumn <= bottomRight.column()) + || (topLeft.column() <= bonusColumn && bonusColumn <= bottomRight.column())) + { + match = true; // maybe the whole table changed + } + } + + if (!match) + return; + + // update autocalculated attributes/skills of every NPC with matching race + int idColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + for (int raceRow = topLeft.parent().row(); raceRow <= bottomRight.parent().row(); ++raceRow) + { + clearNpcStatsCache(); + + std::string raceId = + raceModel->data(raceModel->index(raceRow, idColumn)).toString().toUtf8().constData(); + emit updateNpcAutocalc(2/*race*/, raceId); + } +} + +// FIXME: currently ignoring level changes +void CSMWorld::Data::npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + clearNpcStatsCache(); + + // Either autoCalc flag changed or NPC level changed + CSMWorld::IdTree *objectModel = + static_cast(getTableModel(CSMWorld::UniversalId::Type_Referenceable)); + + int autoCalcColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_AutoCalc); + int miscColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_NpcMisc); + + // first check for level + bool levelChanged = false; + if (topLeft.parent().isValid() && bottomRight.parent().isValid()) + { + if (topLeft.parent().column() <= miscColumn && miscColumn <= bottomRight.parent().column()) + { + for (int col = topLeft.column(); col <= bottomRight.column(); ++col) + { + int role = objectModel->nestedHeaderData(topLeft.parent().column(), + col, Qt::Horizontal, CSMWorld::ColumnBase::Role_ColumnId).toInt(); + if (role == CSMWorld::Columns::ColumnId_NpcLevel) + { + levelChanged = true; + break; + } + } + } + } + + // next check for autocalc + bool autoCalcChanged = false; + if (!topLeft.parent().isValid() && !bottomRight.parent().isValid()) + { + if ((topLeft.column() <= autoCalcColumn && autoCalcColumn <= bottomRight.column()) + || (topLeft.column() <= miscColumn && miscColumn <= bottomRight.column())) + { + autoCalcChanged = true; + } + } + + if (!levelChanged && !autoCalcChanged) + return; + + int row = 0; + int end = 0; + if (topLeft.parent().isValid()) + row = topLeft.parent().row(); + else + row = topLeft.row(); + + if (bottomRight.parent().isValid()) + end = bottomRight.parent().row(); + else + end = bottomRight.row(); + + for (; row <= end; ++row) + { + Record record = + static_cast&>(mReferenceables.getRecord(row)); + ESM::NPC &npc = record.get(); + + // If going from autocalc to non-autocalc, save the autocalc values + if (autoCalcChanged) + { + if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + saveAutoCalcValues(npc); // update attributes and skills + else + npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc + + record.setModified(npc); + mReferenceables.replace(row, record); + } + } +} + +void CSMWorld::Data::gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + static const QStringList gmsts(QStringList()<< "fNPCbaseMagickaMult" << "fAutoSpellChance" + << "fEffectCostMult" << "iAutoSpellAlterationMax" << "iAutoSpellConjurationMax" + << "iAutoSpellDestructionMax" << "iAutoSpellIllusionMax" << "iAutoSpellMysticismMax" + << "iAutoSpellRestorationMax" << "iAutoSpellTimesCanCast" << "iAutoSpellAttSkillMin"); + + bool match = false; + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) + { + if (gmsts.contains(mGmsts.getRecord(row).get().mId.c_str())) + { + match = true; + break; + } + } + + if (!match) + return; + + clearNpcStatsCache(); + + std::string empty; + emit updateNpcAutocalc(0/*all*/, empty); +} + +// FIXME: how to undo? +void CSMWorld::Data::saveAutoCalcValues(ESM::NPC& npc) +{ + CSMWorld::NpcStats * cachedStats = getCachedNpcData (npc.mId); + if (!cachedStats) + return; // silently fail + + CSMWorld::NpcStats* stats = npcAutoCalculate(npc); + + // update npc + npc.mNpdt52.mLevel = npc.mNpdt12.mLevel; + + npc.mNpdt52.mStrength = stats->getBaseAttribute(ESM::Attribute::Strength); + npc.mNpdt52.mIntelligence = stats->getBaseAttribute(ESM::Attribute::Intelligence); + npc.mNpdt52.mWillpower = stats->getBaseAttribute(ESM::Attribute::Willpower); + npc.mNpdt52.mAgility = stats->getBaseAttribute(ESM::Attribute::Agility); + npc.mNpdt52.mSpeed = stats->getBaseAttribute(ESM::Attribute::Speed); + npc.mNpdt52.mEndurance = stats->getBaseAttribute(ESM::Attribute::Endurance); + npc.mNpdt52.mPersonality = stats->getBaseAttribute(ESM::Attribute::Personality); + npc.mNpdt52.mLuck = stats->getBaseAttribute(ESM::Attribute::Luck); + + for (int i = 0; i < ESM::Skill::Length; ++i) + { + npc.mNpdt52.mSkills[i] = stats->getBaseSkill(i); + } + + npc.mNpdt52.mHealth = stats->getHealth(); + npc.mNpdt52.mMana = stats->getMana(); + npc.mNpdt52.mFatigue = stats->getFatigue(); + npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition; + npc.mNpdt52.mReputation = npc.mNpdt12.mReputation; + npc.mNpdt52.mRank = npc.mNpdt12.mRank; + npc.mNpdt52.mGold = npc.mNpdt12.mGold; + + // TODO: add spells from autogenerated list like vanilla (but excluding any race powers or abilities) +} + +void CSMWorld::Data::clearNpcStatsCache () +{ + for (std::map::iterator it (mNpcStatCache.begin()); + it != mNpcStatCache.end(); ++it) + delete it->second; + + mNpcStatCache.clear(); +} + +CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const +{ + CSMWorld::NpcStats * cachedStats = getCachedNpcData (npc.mId); + if (cachedStats) + return cachedStats; + + const ESM::Race *race = &mRaces.getRecord(npc.mRace).get(); + const ESM::Class *class_ = &mClasses.getRecord(npc.mClass).get(); + + bool autoCalc = npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; + short level = npc.mNpdt52.mLevel; + if (autoCalc) + level = npc.mNpdt12.mLevel; + + CSMWorld::NpcStats *stats = new CSMWorld::NpcStats(); + + CSStore store(mGmsts, mSkills, mMagicEffects, mSpells); + + if (autoCalc) + { + GamePlay::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store); + + stats->setHealth(autoCalculateHealth(level, class_, *stats)); + stats->setMana(autoCalculateMana(*stats)); + stats->setFatigue(autoCalculateFatigue(*stats)); + + GamePlay::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store); + + GamePlay::autoCalculateSpells(race, *stats, &store); + } + else + { + for (std::vector::const_iterator it = npc.mSpells.mList.begin(); + it != npc.mSpells.mList.end(); ++it) + { + stats->addSpells(*it); + } + } + + // update spell info + const std::vector &racePowers = race->mPowers.mList; + for (unsigned int i = 0; i < racePowers.size(); ++i) + { + int type = -1; + int spellIndex = mSpells.searchId(racePowers[i]); + if (spellIndex != -1) + type = mSpells.getRecord(spellIndex).get().mData.mType; + stats->addPowers(racePowers[i], type); + } + // cost/chance + int skills[ESM::Skill::Length]; + if (autoCalc) + for (int i = 0; i< ESM::Skill::Length; ++i) + skills[i] = stats->getBaseSkill(i); + else + for (int i = 0; i< ESM::Skill::Length; ++i) + skills[i] = npc.mNpdt52.mSkills[i]; + + int attributes[ESM::Attribute::Length]; + if (autoCalc) + for (int i = 0; i< ESM::Attribute::Length; ++i) + attributes[i] = stats->getBaseAttribute(i); + else + { + attributes[ESM::Attribute::Strength] = npc.mNpdt52.mStrength; + attributes[ESM::Attribute::Willpower] = npc.mNpdt52.mWillpower; + attributes[ESM::Attribute::Agility] = npc.mNpdt52.mAgility; + attributes[ESM::Attribute::Speed] = npc.mNpdt52.mSpeed; + attributes[ESM::Attribute::Endurance] = npc.mNpdt52.mEndurance; + attributes[ESM::Attribute::Personality] = npc.mNpdt52.mPersonality; + attributes[ESM::Attribute::Luck] = npc.mNpdt52.mLuck; + } + + const std::vector& spells = stats->spells(); + for (std::vector::const_iterator it = spells.begin(); it != spells.end(); ++it) + { + int cost = -1; + int spellIndex = mSpells.searchId((*it).mName); + const ESM::Spell* spell = 0; + if (spellIndex != -1) + { + spell = &mSpells.getRecord(spellIndex).get(); + cost = spell->mData.mCost; + + int school; + float skillTerm; + GamePlay::calcWeakestSchool(spell, skills, school, skillTerm, &store); + float chance = calcAutoCastChance(spell, skills, attributes, school, &store); + + stats->addCostAndChance((*it).mName, cost, (int)ceil(chance)); // percent + } + } + + emit cacheNpcStats (npc.mId, stats); + return stats; +} + +void CSMWorld::Data::cacheNpcStatsEvent (const std::string& id, CSMWorld::NpcStats *stats) +{ + mNpcStatCache[id] = stats; +} + +CSMWorld::NpcStats* CSMWorld::Data::getCachedNpcData (const std::string& id) const +{ + std::map::const_iterator it = mNpcStatCache.find(id); + if (it != mNpcStatCache.end()) + return it->second; + else + return 0; +} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index f83f9de22..fdc76fd73 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -60,6 +60,7 @@ namespace CSMWorld { class ResourcesManager; class Resources; + class NpcStats; class Data : public QObject { @@ -108,6 +109,8 @@ namespace CSMWorld std::vector > mReaders; + std::map mNpcStatCache; + // not implemented Data (const Data&); Data& operator= (const Data&); @@ -121,7 +124,11 @@ namespace CSMWorld static int count (RecordBase::State state, const CollectionBase& collection); - const CSMWorld::Data& self (); + const Data& self (); + + void saveAutoCalcValues(ESM::NPC& npc); + + void clearNpcStatsCache (); public: @@ -277,15 +284,37 @@ namespace CSMWorld std::string getAuthor() const; + NpcStats* npcAutoCalculate (const ESM::NPC& npc) const; + + NpcStats* getCachedNpcData (const std::string& id) const; + signals: void idListChanged(); + // refresh NPC dialogue subviews via object table model + void updateNpcAutocalc (int type, const std::string& id); + + void cacheNpcStats (const std::string& id, NpcStats *stats) const; + private slots: void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void rowsChanged (const QModelIndex& parent, int start, int end); + + // for autocalc updates when gmst/race/class/skils tables change + void gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void cacheNpcStatsEvent (const std::string& id, NpcStats *stats); }; } diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 1e81d6ac2..fba672108 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -261,3 +261,8 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::IdTree::nestedTable(const QModelInde return mNestedCollection->nestedTable(index.row(), index.column()); } + +void CSMWorld::IdTree::updateNpcAutocalc (int type, const std::string& id) +{ + emit refreshNpcDialogue (type, id); +} diff --git a/apps/opencs/model/world/idtree.hpp b/apps/opencs/model/world/idtree.hpp index 5337ed82b..b29a3ae93 100644 --- a/apps/opencs/model/world/idtree.hpp +++ b/apps/opencs/model/world/idtree.hpp @@ -73,11 +73,17 @@ namespace CSMWorld virtual bool hasChildren (const QModelIndex& index) const; - signals: + signals: - void resetStart(const QString& id); + void resetStart(const QString& id); - void resetEnd(const QString& id); + void resetEnd(const QString& id); + + void refreshNpcDialogue (int type, const std::string& id); + + public slots: + + void updateNpcAutocalc (int type, const std::string& id); }; } diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 86f0461af..085c77ca3 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -9,191 +9,8 @@ #include "nestedtablewrapper.hpp" #include "usertype.hpp" - -namespace -{ - -int is_even(double d) -{ - double int_part; - - modf(d / 2.0, &int_part); - return 2.0 * int_part == d; -} - -int round_ieee_754(double d) -{ - double i = floor(d); - d -= i; - - if(d < 0.5) - return static_cast(i); - if(d > 0.5) - return static_cast(i) + 1; - if(is_even(i)) - return static_cast(i); - return static_cast(i) + 1; -} - -std::vector autoCalculateAttributes (const ESM::NPC &npc, - const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection& skillTable) -{ - // race bonus - bool male = (npc.mFlags & ESM::NPC::Female) == 0; - - if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) - return std::vector(); - - short level = npc.mNpdt12.mLevel; - - int attr[ESM::Attribute::Length]; - - for (int i = 0; i < ESM::Attribute::Length; ++i) - { - const ESM::Race::MaleFemale& attribute = race.mData.mAttributeValues[i]; - attr[i] = male ? attribute.mMale : attribute.mFemale; - } - - // class bonus - for (int i = 0; i < 2; ++i) - { - int attribute = class_.mData.mAttribute[i]; - if (attribute >= 0 && attribute < ESM::Attribute::Length) - { - attr[attribute] = attr[attribute] + 10; - } - // else log an error - } - - std::vector result(ESM::Attribute::Length); - // skill bonus - for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute) - { - float modifierSum = 0; - - for (int j = 0; j < ESM::Skill::Length; ++j) - { - // if the skill does not exist, throws std::runtime_error ("invalid ID: " + id) - const ESM::Skill& skill = skillTable.getRecord(ESM::Skill::indexToId(j)).get(); - - if (skill.mData.mAttribute != attribute) - continue; - - // is this a minor or major skill? - float add = 0.2f; - for (int k = 0; k < 5; ++k) - { - if (class_.mData.mSkills[k][0] == j) - add = 0.5; - } - for (int k = 0; k < 5; ++k) - { - if (class_.mData.mSkills[k][1] == j) - add = 1.0; - } - modifierSum += add; - } - result[attribute] = std::min(round_ieee_754(attr[attribute] + (level-1) * modifierSum), 100); - } - - return result; -} - -std::vector autoCalculateSkills (const ESM::NPC &npc, - const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection& skillTable) -{ - unsigned char skills[ESM::Skill::Length]; - for (int i = 0; i < ESM::Skill::Length; ++i) - skills[i] = 0; - - for (int i = 0; i < 2; ++i) - { - int bonus = (i == 0) ? 10 : 25; - - for (int i2 = 0; i2 < 5; ++i2) - { - int index = class_.mData.mSkills[i2][i]; - if (index >= 0 && index < ESM::Skill::Length) - { - skills[index] = bonus; - } - } - } - - std::vector result(ESM::Skill::Length); - for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) - { - float majorMultiplier = 0.1f; - float specMultiplier = 0.0f; - - int raceBonus = 0; - int specBonus = 0; - - for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) - { - if (race.mData.mBonus[raceSkillIndex].mSkill == skillIndex) - { - raceBonus = race.mData.mBonus[raceSkillIndex].mBonus; - break; - } - } - - for (int k = 0; k < 5; ++k) - { - // is this a minor or major skill? - if ((class_.mData.mSkills[k][0] == skillIndex) || (class_.mData.mSkills[k][1] == skillIndex)) - { - majorMultiplier = 1.0f; - break; - } - } - - // is this skill in the same Specialization as the class? - const ESM::Skill& skill = skillTable.getRecord(ESM::Skill::indexToId(skillIndex)).get(); - if (skill.mData.mSpecialization == class_.mData.mSpecialization) - { - specMultiplier = 0.5f; - specBonus = 5; - } - - // Must gracefully handle level 0 - result[skillIndex] = std::min(round_ieee_754(skills[skillIndex] + 5 + raceBonus + specBonus + - (npc.mNpdt12.mLevel-1) * (majorMultiplier + specMultiplier)), 100); - } - - return result; -} - -unsigned short autoCalculateHealth(const ESM::NPC &npc, - const ESM::Class& class_, const std::vector& attr) -{ - int multiplier = 3; - - if (class_.mData.mSpecialization == ESM::Class::Combat) - multiplier += 2; - else if (class_.mData.mSpecialization == ESM::Class::Stealth) - multiplier += 1; - - if (class_.mData.mAttribute[0] == ESM::Attribute::Endurance - || class_.mData.mAttribute[1] == ESM::Attribute::Endurance) - multiplier += 1; - - return floor(0.5f * (attr[ESM::Attribute::Strength]+ attr[ESM::Attribute::Endurance])) - + multiplier * (npc.mNpdt12.mLevel-1); -} - -unsigned short autoCalculateMana(const std::vector& attr) -{ - return attr[ESM::Attribute::Intelligence] * 2; -} - -unsigned short autoCalculateFatigue(const std::vector& attr) -{ - return attr[ESM::Attribute::Strength] + attr[ESM::Attribute::Willpower] - + attr[ESM::Attribute::Agility] + attr[ESM::Attribute::Endurance]; -} - -} +#include "idtree.hpp" +#include "npcstats.hpp" CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) : InventoryColumns (columns) {} @@ -741,12 +558,8 @@ CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) mMisc(NULL) {} -CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns, - const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& classes, - const CSMWorld::IdCollection& skills) -: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns), - mRaceTable(races), mClassTable(classes), mSkillTable(skills) +CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) +: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns) {} QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) @@ -821,46 +634,8 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d npc.mFlags &= ~iter->second; if (iter->second == ESM::NPC::Autocalc) - { - if(value.toInt() != 0) - { - npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; - - // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) - const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); - const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); - std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); - if (attr.empty()) - return; - - std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); - - ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; - - npcStruct.mLevel = npc.mNpdt12.mLevel; - npcStruct.mStrength = attr[ESM::Attribute::Strength]; - npcStruct.mIntelligence = attr[ESM::Attribute::Intelligence]; - npcStruct.mWillpower = attr[ESM::Attribute::Willpower]; - npcStruct.mAgility = attr[ESM::Attribute::Agility]; - npcStruct.mSpeed = attr[ESM::Attribute::Speed]; - npcStruct.mEndurance = attr[ESM::Attribute::Endurance]; - npcStruct.mPersonality = attr[ESM::Attribute::Personality]; - npcStruct.mLuck = attr[ESM::Attribute::Luck]; - for (int i = 0; i < ESM::Skill::Length; ++i) - { - npcStruct.mSkills[i] = skills[i]; - } - npcStruct.mHealth = autoCalculateHealth(npc, class_, attr); - npcStruct.mMana = autoCalculateMana(attr); - npcStruct.mFatigue = autoCalculateFatigue(attr); - npcStruct.mDisposition = npc.mNpdt12.mDisposition; - npcStruct.mReputation = npc.mNpdt12.mReputation; - npcStruct.mRank = npc.mNpdt12.mRank; - npcStruct.mGold = npc.mNpdt12.mGold; - } - else - npc.mNpdtType = ESM::NPC::NPC_DEFAULT; - } + npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS + : ESM::NPC::NPC_DEFAULT; } else { @@ -873,9 +648,7 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d record.setModified (npc); } -CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter(const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& classes, const CSMWorld::IdCollection& skills) - : mRaceTable(races), mClassTable(classes), mSkillTable(skills) +CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter(const CSMWorld::Data& data) : mData(data) {} void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, @@ -940,27 +713,20 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * default: return QVariant(); // throw an exception here? } else if (subColIndex == 1) - // It may be possible to have mNpdt52 values different to autocalculated ones when - // first loaded, so re-calculate if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); - const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); - std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); - - if (attr.empty()) - return QVariant(); + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); switch (subRowIndex) { - case 0: return static_cast(attr[ESM::Attribute::Strength]); - case 1: return static_cast(attr[ESM::Attribute::Intelligence]); - case 2: return static_cast(attr[ESM::Attribute::Willpower]); - case 3: return static_cast(attr[ESM::Attribute::Agility]); - case 4: return static_cast(attr[ESM::Attribute::Speed]); - case 5: return static_cast(attr[ESM::Attribute::Endurance]); - case 6: return static_cast(attr[ESM::Attribute::Personality]); - case 7: return static_cast(attr[ESM::Attribute::Luck]); + case 0: return static_cast(stats->getBaseAttribute(ESM::Attribute::Strength)); + case 1: return static_cast(stats->getBaseAttribute(ESM::Attribute::Intelligence)); + case 2: return static_cast(stats->getBaseAttribute(ESM::Attribute::Willpower)); + case 3: return static_cast(stats->getBaseAttribute(ESM::Attribute::Agility)); + case 4: return static_cast(stats->getBaseAttribute(ESM::Attribute::Speed)); + case 5: return static_cast(stats->getBaseAttribute(ESM::Attribute::Endurance)); + case 6: return static_cast(stats->getBaseAttribute(ESM::Attribute::Personality)); + case 7: return static_cast(stats->getBaseAttribute(ESM::Attribute::Luck)); default: return QVariant(); // throw an exception here? } } @@ -1019,10 +785,8 @@ int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *c return 8; } -CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter(const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& classes, - const CSMWorld::IdCollection& skills) - : mRaceTable(races), mClassTable(classes), mSkillTable(skills) +CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter(const CSMWorld::Data& data) + : mData(data) {} void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow (const RefIdColumn *column, @@ -1079,18 +843,10 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu return QString(ESM::Skill::sSkillNames[subRowIndex].c_str()); else if (subColIndex == 1) { - // It may be possible to have mNpdt52 values different to autocalculated ones when - // first loaded, so re-calculate if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) - const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); - const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); - std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); - - int value = static_cast(skills[subRowIndex]); - - return static_cast(skills[subRowIndex]); + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + return static_cast(stats->getBaseSkill(subRowIndex)); } else { @@ -1132,10 +888,7 @@ int CSMWorld::NpcSkillsRefIdAdapter::getNestedRowsCount(const RefIdColumn *colum return ESM::Skill::Length; } -CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter(const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& classes, - const CSMWorld::IdCollection& skills) - : mRaceTable(races), mClassTable(classes), mSkillTable(skills) +CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter(const CSMWorld::Data& data) : mData(data) {} CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter() @@ -1175,17 +928,9 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column bool autoCalc = (npc.mFlags & ESM::NPC::Autocalc) != 0; - // It may be possible to have mNpdt52 values different to autocalculated ones when - // first loaded, so re-calculate if (autoCalc) { - // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) - const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); - const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); - std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); - - if (attr.empty()) - return QVariant(); + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); switch (subColIndex) { @@ -1197,17 +942,17 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column } case 2: { - UserInt i(autoCalculateHealth(npc, class_, attr)); + UserInt i(stats->getHealth()); return QVariant(QVariant::fromValue(i)); } case 3: { - UserInt i(autoCalculateMana(attr)); + UserInt i(stats->getMana()); return QVariant(QVariant::fromValue(i)); } case 4: { - UserInt i(autoCalculateFatigue(attr)); + UserInt i(stats->getFatigue()); return QVariant(QVariant::fromValue(i)); } case 5: return static_cast(record.get().mNpdt12.mDisposition); @@ -1247,108 +992,31 @@ void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column, if (autoCalc) switch(subColIndex) { - case 0: - { - npc.mNpdt12.mLevel = static_cast(value.toInt()); break; - - const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); - const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); - std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); - if (attr.empty()) - return; - - ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; - - std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); - - npcStruct.mLevel = npc.mNpdt12.mLevel; - npcStruct.mStrength = attr[ESM::Attribute::Strength]; - npcStruct.mIntelligence = attr[ESM::Attribute::Intelligence]; - npcStruct.mWillpower = attr[ESM::Attribute::Willpower]; - npcStruct.mAgility = attr[ESM::Attribute::Agility]; - npcStruct.mSpeed = attr[ESM::Attribute::Speed]; - npcStruct.mEndurance = attr[ESM::Attribute::Endurance]; - npcStruct.mPersonality = attr[ESM::Attribute::Personality]; - npcStruct.mLuck = attr[ESM::Attribute::Luck]; - for (int i = 0; i < ESM::Skill::Length; ++i) - { - npcStruct.mSkills[i] = skills[i]; - } - npcStruct.mHealth = autoCalculateHealth(npc, class_, attr); - npcStruct.mMana = autoCalculateMana(attr); - npcStruct.mFatigue = autoCalculateFatigue(attr); - - break; - } + case 0: npc.mNpdt12.mLevel = static_cast(value.toInt()); break; case 1: return; case 2: return; case 3: return; case 4: return; - case 5: - { - npc.mNpdt12.mDisposition = static_cast(value.toInt()); - npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition; - break; - } - case 6: - { - npc.mNpdt12.mReputation = static_cast(value.toInt()); - npc.mNpdt52.mReputation = npc.mNpdt12.mReputation; - break; - } - case 7: - { - npc.mNpdt12.mRank = static_cast(value.toInt()); - npc.mNpdt52.mRank = npc.mNpdt12.mRank; - break; - } - case 8: - { - npc.mNpdt12.mGold = value.toInt(); - npc.mNpdt52.mGold = npc.mNpdt12.mGold; - break; - } - case 9: npc.mPersistent = value.toBool(); break; + case 5: npc.mNpdt12.mDisposition = static_cast(value.toInt()); break; + case 6: npc.mNpdt12.mReputation = static_cast(value.toInt()); break; + case 7: npc.mNpdt12.mRank = static_cast(value.toInt()); break; + case 8: npc.mNpdt12.mGold = value.toInt(); break; + case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } else switch(subColIndex) { - case 0: - { - npc.mNpdt52.mLevel = static_cast(value.toInt()); - npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; - break; - } - case 1: npc.mNpdt52.mFactionID = static_cast(value.toInt()); break; - case 2: npc.mNpdt52.mHealth = static_cast(value.toInt()); break; - case 3: npc.mNpdt52.mMana = static_cast(value.toInt()); break; - case 4: npc.mNpdt52.mFatigue = static_cast(value.toInt()); break; - case 5: - { - npc.mNpdt52.mDisposition = static_cast(value.toInt()); - npc.mNpdt12.mDisposition = npc.mNpdt52.mDisposition; - break; - } - case 6: - { - npc.mNpdt52.mReputation = static_cast(value.toInt()); - npc.mNpdt12.mReputation = npc.mNpdt52.mReputation; - break; - } - case 7: - { - npc.mNpdt52.mRank = static_cast(value.toInt()); - npc.mNpdt12.mRank = npc.mNpdt52.mRank; - break; - } - case 8: - { - npc.mNpdt52.mGold = value.toInt(); - npc.mNpdt12.mGold = npc.mNpdt52.mGold; - break; - } - case 9: npc.mPersistent = value.toBool(); break; + case 0: npc.mNpdt52.mLevel = static_cast(value.toInt()); break; + case 1: npc.mNpdt52.mFactionID = static_cast(value.toInt()); break; + case 2: npc.mNpdt52.mHealth = static_cast(value.toInt()); break; + case 3: npc.mNpdt52.mMana = static_cast(value.toInt()); break; + case 4: npc.mNpdt52.mFatigue = static_cast(value.toInt()); break; + case 5: npc.mNpdt52.mDisposition = static_cast(value.toInt()); break; + case 6: npc.mNpdt52.mReputation = static_cast(value.toInt()); break; + case 7: npc.mNpdt52.mRank = static_cast(value.toInt()); break; + case 8: npc.mNpdt52.mGold = value.toInt(); break; + case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } @@ -1453,3 +1121,227 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData EnchantableRefIdAdapter::setData (column, data, index, value); } } + +void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return; // can't edit autocalculated spells + + ESM::NPC caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + std::string newString; + + if (position >= (int)list.size()) + list.push_back(newString); + else + list.insert(list.begin()+position, newString); + + record.setModified (caster); +} + +void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return; // can't edit autocalculated spells + + ESM::NPC caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + // avoid race power rows + int size = 0; + int raceIndex = mData.getRaces().searchId(caster.mRace); + if (raceIndex != -1) + size = mData.getRaces().getRecord(raceIndex).get().mPowers.mList.size(); + + if (rowToRemove < 0 || rowToRemove >= static_cast (list.size() + size)) + throw std::runtime_error ("index out of range"); + + if (rowToRemove >= static_cast(list.size()) && rowToRemove < static_cast(list.size() + size)) + return; // hack, assumes the race powers are added at the end + + list.erase (list.begin () + rowToRemove); + + record.setModified (caster); +} + +void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + + if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return; // can't edit autocalculated spells + + ESM::NPC caster = record.get(); + std::vector& list = caster.mSpells.mList; + + // avoid race power rows + int size = 0; + int raceIndex = mData.getRaces().searchId(caster.mRace); + if (raceIndex != -1) + size = mData.getRaces().getRecord(raceIndex).get().mPowers.mList.size(); + + if (subRowIndex < 0 || subRowIndex >= static_cast (list.size() + size)) + throw std::runtime_error ("index out of range"); + + if (subRowIndex >= static_cast(list.size()) && subRowIndex < static_cast(list.size() + size)) + return; // hack, assumes the race powers are added at the end + + if (subColIndex == 0) + list.at(subRowIndex) = std::string(value.toString().toUtf8()); + else + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + + record.setModified (caster); +} + +QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + const std::vector& spells = mData.npcAutoCalculate(record.get())->spells(); + + if (subRowIndex < 0 || subRowIndex >= static_cast (spells.size())) + throw std::runtime_error ("index out of range"); + + switch (subColIndex) + { + case 0: return QString::fromUtf8(spells[subRowIndex].mName.c_str()); + case 1: return spells[subRowIndex].mType; + case 2: return spells[subRowIndex].mFromRace; + case 3: return spells[subRowIndex].mCost; + case 4: return spells[subRowIndex].mChance; + default: + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + } +} + +int CSMWorld::NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, + const RefIdData& data) const +{ + return 5; +} + +int CSMWorld::NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + const std::vector spells = mData.npcAutoCalculate(record.get())->spells(); + return static_cast(spells.size()); +} + +template <> +void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + ESM::Creature caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + std::string newString; + + if (position >= (int)list.size()) + list.push_back(newString); + else + list.insert(list.begin()+position, newString); + + record.setModified (caster); +} + +template <> +void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + ESM::Creature caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) + throw std::runtime_error ("index out of range"); + + list.erase (list.begin () + rowToRemove); + + record.setModified (caster); +} + +template <> +void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + ESM::Creature caster = record.get(); + std::vector& list = caster.mSpells.mList; + + if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) + throw std::runtime_error ("index out of range"); + + if (subColIndex == 0) + list.at(subRowIndex) = std::string(value.toString().toUtf8()); + else + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + + record.setModified (caster); +} + +template<> +QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + const std::vector& list = record.get().mSpells.mList; + + if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) + throw std::runtime_error ("index out of range"); + + const std::string& content = list.at(subRowIndex); + + int type = -1; + int spellIndex = mData.getSpells().searchId(content); + if (spellIndex != -1) + type = mData.getSpells().getRecord(spellIndex).get().mData.mType; + + if (subColIndex == 0) + return QString::fromUtf8(content.c_str()); + else if (subColIndex == 1) + return type; + else + throw std::runtime_error("Trying to access non-existing column in the nested table!"); +} + +template <> +int CSMWorld::NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, + const RefIdData& data) const +{ + return 2; +} + +template <> +int CSMWorld::NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + return static_cast(record.get().mSpells.mList.size()); +} diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index b12f84d5e..acdc124eb 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -21,6 +21,7 @@ #include "refidadapter.hpp" #include "nestedtablewrapper.hpp" #include "idcollection.hpp" +#include "data.hpp" namespace CSMWorld { @@ -806,16 +807,10 @@ namespace CSMWorld class NpcRefIdAdapter : public ActorRefIdAdapter { NpcColumns mColumns; - const IdCollection& mRaceTable; - const IdCollection& mClassTable; - const IdCollection& mSkillTable; public: - NpcRefIdAdapter (const NpcColumns& columns, - const IdCollection& races, - const IdCollection& classes, - const IdCollection& skills); + NpcRefIdAdapter (const NpcColumns& columns); virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const; @@ -860,15 +855,11 @@ namespace CSMWorld class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase { - const IdCollection& mRaceTable; - const IdCollection& mClassTable; - const IdCollection& mSkillTable; + const Data& mData; public: - NpcAttributesRefIdAdapter (const IdCollection& races, - const IdCollection& classes, - const IdCollection& skills); + NpcAttributesRefIdAdapter (const Data& data); virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const; @@ -895,15 +886,11 @@ namespace CSMWorld class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase { - const IdCollection& mRaceTable; - const IdCollection& mClassTable; - const IdCollection& mSkillTable; + const Data& mData; public: - NpcSkillsRefIdAdapter (const IdCollection& races, - const IdCollection& classes, - const IdCollection& skills); + NpcSkillsRefIdAdapter (const Data& data); virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const; @@ -930,18 +917,14 @@ namespace CSMWorld class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase { - const IdCollection& mRaceTable; - const IdCollection& mClassTable; - const IdCollection& mSkillTable; + const Data& mData; NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&); NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&); public: - NpcMiscRefIdAdapter (const IdCollection& races, - const IdCollection& classes, - const IdCollection& skills); + NpcMiscRefIdAdapter (const Data& data); virtual ~NpcMiscRefIdAdapter(); virtual void addNestedRow (const RefIdColumn *column, @@ -1189,6 +1172,7 @@ namespace CSMWorld class NestedSpellRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; + const Data& mData; // not implemented NestedSpellRefIdAdapter (const NestedSpellRefIdAdapter&); @@ -1196,45 +1180,15 @@ namespace CSMWorld public: - NestedSpellRefIdAdapter(UniversalId::Type type) :mType(type) {} + NestedSpellRefIdAdapter(UniversalId::Type type, const Data& data) :mType(type), mData(data) {} virtual ~NestedSpellRefIdAdapter() {} virtual void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const - { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - ESXRecordT caster = record.get(); - - std::vector& list = caster.mSpells.mList; - - std::string newString; - - if (position >= (int)list.size()) - list.push_back(newString); - else - list.insert(list.begin()+position, newString); - - record.setModified (caster); - } + RefIdData& data, int index, int position) const; virtual void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const - { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - ESXRecordT caster = record.get(); - - std::vector& list = caster.mSpells.mList; - - if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); - - list.erase (list.begin () + rowToRemove); - - record.setModified (caster); - } + RefIdData& data, int index, int rowToRemove) const; virtual void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const @@ -1260,55 +1214,14 @@ namespace CSMWorld } virtual QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const - { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - - const std::vector& list = record.get().mSpells.mList; - - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); - - const std::string& content = list.at(subRowIndex); - - if (subColIndex == 0) - return QString::fromUtf8(content.c_str()); - else - throw std::runtime_error("Trying to access non-existing column in the nested table!"); - } + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; virtual void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const - { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); - ESXRecordT caster = record.get(); - std::vector& list = caster.mSpells.mList; - - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); - - if (subColIndex == 0) - list.at(subRowIndex) = std::string(value.toString().toUtf8()); - else - throw std::runtime_error("Trying to access non-existing column in the nested table!"); - - record.setModified (caster); - } - - virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const - { - return 1; - } + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; - virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const - { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; - return static_cast(record.get().mSpells.mList.size()); - } + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; }; template diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 739e9cec4..d5232db63 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -146,12 +146,21 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) actorsColumns.mSpells = &mColumns.back(); std::map spellsMap; spellsMap.insert(std::make_pair(UniversalId::Type_Npc, - new NestedSpellRefIdAdapter (UniversalId::Type_Npc))); + new NestedSpellRefIdAdapter (UniversalId::Type_Npc, data))); spellsMap.insert(std::make_pair(UniversalId::Type_Creature, - new NestedSpellRefIdAdapter (UniversalId::Type_Creature))); + new NestedSpellRefIdAdapter (UniversalId::Type_Creature, data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), spellsMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_SpellId, CSMWorld::ColumnBase::Display_Spell)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellType, CSMWorld::ColumnBase::Display_SpellType, false/*editable*/, false/*user editable*/)); + // creatures do not have below columns + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellSrc, CSMWorld::ColumnBase::Display_YesNo, false, false)); // from race + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellCost, CSMWorld::ColumnBase::Display_Integer, false, false)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellChance, CSMWorld::ColumnBase::Display_Integer/*Percent*/, false, false)); // Nested table mColumns.push_back(RefIdColumn (Columns::ColumnId_NpcDestinations, @@ -438,7 +447,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); npcColumns.mAttributes = &mColumns.back(); std::map attrMap; - attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); + attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attrMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcAttributes, CSMWorld::ColumnBase::Display_String, false, false)); @@ -450,7 +459,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); npcColumns.mSkills = &mColumns.back(); std::map skillsMap; - skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); + skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcSkills, CSMWorld::ColumnBase::Display_String, false, false)); @@ -462,7 +471,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); npcColumns.mMisc = &mColumns.back(); std::map miscMap; - miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); + miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), miscMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer, @@ -611,7 +620,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous, new MiscRefIdAdapter (inventoryColumns, key))); mAdapters.insert (std::make_pair (UniversalId::Type_Npc, - new NpcRefIdAdapter (npcColumns, data.getRaces(), data.getClasses(), data.getSkills()))); + new NpcRefIdAdapter (npcColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Probe, new ToolRefIdAdapter (UniversalId::Type_Probe, toolsColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Repair, diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index f11079ac4..34e310a49 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -678,6 +678,14 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM connect(mEditWidget, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), this, SLOT(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); + if (id.getType() == CSMWorld::UniversalId::Type_Referenceable) + { + CSMWorld::IdTree *objectTable = static_cast(mTable); + + connect (objectTable, SIGNAL (refreshNpcDialogue (int, const std::string&)), + this, SLOT (refreshNpcDialogue (int, const std::string&))); + } + mMainLayout->addWidget(mEditWidget); mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); @@ -897,3 +905,27 @@ void CSVWorld::DialogueSubView::changeCurrentId (const std::string& newId) selection.push_back(mCurrentId); mCommandDispatcher.setSelection(selection); } + +void CSVWorld::DialogueSubView::refreshNpcDialogue (int type, const std::string& id) +{ + int typeColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + if (CSMWorld::UniversalId::Type_Npc + != mTable->data(mTable->getModelIndex(mCurrentId, typeColumn), Qt::DisplayRole).toInt()) + { + return; + } + + int raceColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Race); + int classColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Class); + + if ((type == 0/*FIXME*/ && id == "") // skill or gmst changed + || (id == mTable->data(mTable->getModelIndex(mCurrentId, raceColumn), + Qt::DisplayRole).toString().toUtf8().constData()) // race + || (id == mTable->data(mTable->getModelIndex(mCurrentId, classColumn), + Qt::DisplayRole).toString().toUtf8().constData())) // class + { + int y = mEditWidget->verticalScrollBar()->value(); + mEditWidget->remake (mTable->getModelIndex(mCurrentId, 0).row()); + mEditWidget->verticalScrollBar()->setValue(y); + } +} diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 6cbd8ad77..28b267155 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -237,6 +237,8 @@ namespace CSVWorld void requestFocus (const std::string& id); void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + + void refreshNpcDialogue (int type, const std::string& id); }; } diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c7c701d20..d54152100 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -66,7 +66,7 @@ add_openmw_dir (mwworld cells localscripts customdata weather inventorystore ptr actionopen actionread actionequip timestamp actionalchemy cellstore actionapply actioneat esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor - contentloader esmloader actiontrap cellreflist projectilemanager cellref + contentloader esmloader actiontrap cellreflist projectilemanager cellref mwstore ) add_openmw_dir (mwclass @@ -78,7 +78,7 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting - disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning + disease pickpocket levelledlist combat steering obstacle difficultyscaling aicombataction actor summoning ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 3ca57aca8..4f419c635 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -11,6 +11,10 @@ #include #include +#include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -24,7 +28,6 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/disease.hpp" #include "../mwmechanics/combat.hpp" -#include "../mwmechanics/autocalcspell.hpp" #include "../mwmechanics/difficultyscaling.hpp" #include "../mwmechanics/character.hpp" @@ -36,6 +39,7 @@ #include "../mwworld/customdata.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/mwstore.hpp" #include "../mwrender/actors.hpp" #include "../mwrender/renderinginterface.hpp" @@ -58,116 +62,47 @@ namespace return new NpcCustomData (*this); } - int is_even(double d) { - double int_part; - modf(d / 2.0, &int_part); - return 2.0 * int_part == d; - } - - int round_ieee_754(double d) { - double i = floor(d); - d -= i; - if(d < 0.5) - return static_cast(i); - if(d > 0.5) - return static_cast(i) + 1; - if(is_even(i)) - return static_cast(i); - return static_cast(i) + 1; - } - - void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) + class Stats : public GamePlay::StatsBase { - // race bonus - const ESM::Race *race = - MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); + MWMechanics::CreatureStats& mCreatureStats; + MWMechanics::NpcStats& mNpcStats; - bool male = (npc->mFlags & ESM::NPC::Female) == 0; + public: - int level = creatureStats.getLevel(); - for (int i=0; imData.mAttributeValues[i]; - creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); - } + Stats(MWMechanics::CreatureStats& creatureStats, MWMechanics::NpcStats& npcStats) + : mCreatureStats(creatureStats), mNpcStats(npcStats) {} - // class bonus - const ESM::Class *class_ = - MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); + virtual unsigned char getBaseAttribute(int index) const { return mCreatureStats.getAttribute(index).getBase(); } - for (int i=0; i<2; ++i) - { - int attribute = class_->mData.mAttribute[i]; - if (attribute>=0 && attribute<8) - { - creatureStats.setAttribute(attribute, - creatureStats.getAttribute(attribute).getBase() + 10); - } - } + virtual void setAttribute(int index, unsigned char value) { mCreatureStats.setAttribute(index, value); } - // skill bonus - for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute) - { - float modifierSum = 0; + virtual void addSpells(std::string id) { mCreatureStats.getSpells().add(id); } - for (int j=0; jgetStore().get().find(j); + virtual unsigned char getBaseSkill(int index) const { return mNpcStats.getSkill(index).getBase(); } - if (skill->mData.mAttribute != attribute) - continue; + virtual void setBaseSkill(int index, unsigned char value) { mNpcStats.getSkill(index).setBase(value); } + }; - // is this a minor or major skill? - float add=0.2f; - for (int k=0; k<5; ++k) - { - if (class_->mData.mSkills[k][0] == j) - add=0.5; - } - for (int k=0; k<5; ++k) - { - if (class_->mData.mSkills[k][1] == j) - add=1.0; - } - modifierSum += add; - } - creatureStats.setAttribute(attribute, std::min( - round_ieee_754(creatureStats.getAttribute(attribute).getBase() - + (level-1) * modifierSum), 100) ); - } + void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) + { + const ESM::Race *race = + MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); - // initial health - int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); - int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); + const ESM::Class *class_ = + MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); + + int level = creatureStats.getLevel(); - int multiplier = 3; + MWMechanics::NpcStats dummy; // npc stats are needed for skills, which is not calculated here + Stats stats(creatureStats, dummy); - if (class_->mData.mSpecialization == ESM::Class::Combat) - multiplier += 2; - else if (class_->mData.mSpecialization == ESM::Class::Stealth) - multiplier += 1; + MWWorld::MWStore store; - if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance - || class_->mData.mAttribute[1] == ESM::Attribute::Endurance) - multiplier += 1; + GamePlay::autoCalcAttributesImpl (npc, race, class_, level, stats, &store); - creatureStats.setHealth(floor(0.5f * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1)); + creatureStats.setHealth(GamePlay::autoCalculateHealth(level, class_, stats)); } - /** - * @brief autoCalculateSkills - * - * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ): - * - * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier) - * - * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill. - * - * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class, - * zero for other Skills. - * - * and by adding class, race, specialization bonus. - */ void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr) { const ESM::Class *class_ = @@ -177,77 +112,13 @@ namespace const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); + Stats stats(npcStats, npcStats); - for (int i = 0; i < 2; ++i) - { - int bonus = (i==0) ? 10 : 25; - - for (int i2 = 0; i2 < 5; ++i2) - { - int index = class_->mData.mSkills[i2][i]; - if (index >= 0 && index < ESM::Skill::Length) - { - npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus); - } - } - } - - for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) - { - float majorMultiplier = 0.1f; - float specMultiplier = 0.0f; - - int raceBonus = 0; - int specBonus = 0; - - for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) - { - if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) - { - raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; - break; - } - } - - for (int k = 0; k < 5; ++k) - { - // is this a minor or major skill? - if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) - { - majorMultiplier = 1.0f; - break; - } - } - - // is this skill in the same Specialization as the class? - const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get().find(skillIndex); - if (skill->mData.mSpecialization == class_->mData.mSpecialization) - { - specMultiplier = 0.5f; - specBonus = 5; - } - - npcStats.getSkill(skillIndex).setBase( - std::min( - round_ieee_754( - npcStats.getSkill(skillIndex).getBase() - + 5 - + raceBonus - + specBonus - +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 - } - - int skills[ESM::Skill::Length]; - for (int i=0; i spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); - for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) - npcStats.getSpells().add(*it); + GamePlay::autoCalculateSpells(race, stats, &store); } } @@ -392,7 +263,7 @@ namespace MWClass // store ptr.getRefData().setCustomData (data.release()); - getInventoryStore(ptr).autoEquip(ptr); + getInventoryStore(ptr).autoEquip(ptr); } } diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index c392372ff..c6954d91d 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -140,7 +142,7 @@ namespace MWGui if(world->getStore().get().isDynamic(cls->mId)) { // Choosing Stealth specialization and Speed/Agility as attributes, if possible. Otherwise fall back to first class found. - MWWorld::SharedIterator it = world->getStore().get().begin(); + GamePlay::SharedIterator it = world->getStore().get().begin(); for(; it != world->getStore().get().end(); ++it) { if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index c829154e2..5d61bc1d2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -6,8 +6,11 @@ #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/mwstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -23,7 +26,6 @@ #include #include "spellcasting.hpp" -#include "autocalcspell.hpp" #include @@ -252,10 +254,12 @@ namespace MWMechanics continue; static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->getFloat(); - if (calcAutoCastChance(spell, skills, attributes, -1) < fAutoPCSpellChance) + MWWorld::MWStore store; + + if (GamePlay::calcAutoCastChance(spell, skills, attributes, -1, &store) < fAutoPCSpellChance) continue; - if (!attrSkillCheck(spell, skills, attributes)) + if (!GamePlay::attrSkillCheck(spell, skills, attributes, &store)) continue; selectedSpells.push_back(spell->mId); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 5d9beecb6..61518548b 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -66,7 +66,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) esm.getRecHeader(); // Look up the record type. - std::map::iterator it = mStores.find(n.val); + std::map::iterator it = mStores.find(n.val); if (it == mStores.end()) { if (n.val == ESM::REC_INFO) { @@ -130,7 +130,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) void ESMStore::setUp() { - std::map::iterator it = mStores.begin(); + std::map::iterator it = mStores.begin(); for (; it != mStores.end(); ++it) { it->second->setUp(); } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 05b633956..61d361b3e 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -67,7 +67,7 @@ namespace MWWorld // Lookup of all IDs. Makes looking up references faster. Just // maps the id name to the record type. std::map mIds; - std::map mStores; + std::map mStores; ESM::NPC mPlayerTemplate; @@ -75,7 +75,7 @@ namespace MWWorld public: /// \todo replace with SharedIterator - typedef std::map::const_iterator iterator; + typedef std::map::const_iterator iterator; iterator begin() const { return mStores.begin(); @@ -144,7 +144,7 @@ namespace MWWorld void clearDynamic () { - for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) + for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) it->second->clearDynamic(); mNpcs.insert(mPlayerTemplate); diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index ab09782b1..852092a9a 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -10,6 +10,7 @@ #include #include +#include #include @@ -17,89 +18,10 @@ namespace MWWorld { - struct StoreBase - { - virtual ~StoreBase() {} - - virtual void setUp() {} - virtual void listIdentifier(std::vector &list) const {} - - virtual size_t getSize() const = 0; - virtual int getDynamicSize() const { return 0; } - virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; - - virtual bool eraseStatic(const std::string &id) {return false;} - virtual void clearDynamic() {} - - virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - - virtual void read (ESM::ESMReader& reader, const std::string& id) {} - ///< Read into dynamic storage - }; - - template - class SharedIterator - { - typedef typename std::vector::const_iterator Iter; - - Iter mIter; - - public: - SharedIterator() {} - - SharedIterator(const SharedIterator &orig) - : mIter(orig.mIter) - {} - - SharedIterator(const Iter &iter) - : mIter(iter) - {} - - SharedIterator &operator++() { - ++mIter; - return *this; - } - - SharedIterator operator++(int) { - SharedIterator iter = *this; - ++mIter; - - return iter; - } - - SharedIterator &operator--() { - --mIter; - return *this; - } - - SharedIterator operator--(int) { - SharedIterator iter = *this; - --mIter; - - return iter; - } - - bool operator==(const SharedIterator &x) const { - return mIter == x.mIter; - } - - bool operator!=(const SharedIterator &x) const { - return !(*this == x); - } - - const T &operator*() const { - return **mIter; - } - - const T *operator->() const { - return &(**mIter); - } - }; - class ESMStore; template - class Store : public StoreBase + class Store : public GamePlay::CommonStore { std::map mStatic; std::vector mShared; // Preserves the record order as it came from the content files (this @@ -137,7 +59,7 @@ namespace MWWorld : mStatic(orig.mData) {} - typedef SharedIterator iterator; + typedef GamePlay::SharedIterator iterator; // setUp needs to be called again after virtual void clearDynamic() @@ -380,7 +302,7 @@ namespace MWWorld } template <> - class Store : public StoreBase + class Store : public GamePlay::StoreBase { // For multiple ESM/ESP files we need one list per file. typedef std::vector LandTextureList; @@ -457,7 +379,7 @@ namespace MWWorld }; template <> - class Store : public StoreBase + class Store : public GamePlay::StoreBase { std::vector mStatic; @@ -472,7 +394,7 @@ namespace MWWorld }; public: - typedef SharedIterator iterator; + typedef GamePlay::SharedIterator iterator; virtual ~Store() { @@ -546,7 +468,7 @@ namespace MWWorld }; template <> - class Store : public StoreBase + class Store : public GamePlay::StoreBase { struct DynamicExtCmp { @@ -586,7 +508,7 @@ namespace MWWorld void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); public: - typedef SharedIterator iterator; + typedef GamePlay::SharedIterator iterator; const ESM::Cell *search(const std::string &id) const { ESM::Cell cell; @@ -834,7 +756,7 @@ namespace MWWorld }; template <> - class Store : public StoreBase + class Store : public GamePlay::StoreBase { private: typedef std::map Interior; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 1b33b10f6..8c37c8ef3 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -6,16 +6,16 @@ set (VERSION_HPP ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp) if (GIT_CHECKOUT) add_custom_target (git-version COMMAND ${CMAKE_COMMAND} - -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} - -DVERSION_HPP_IN=${VERSION_HPP_IN} - -DVERSION_HPP=${VERSION_HPP} - -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} - -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} - -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} - -DOPENMW_VERSION=${OPENMW_VERSION} + -DVERSION_HPP_IN=${VERSION_HPP_IN} + -DVERSION_HPP=${VERSION_HPP} + -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} + -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} + -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} + -DOPENMW_VERSION=${OPENMW_VERSION} -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake - VERBATIM) + VERBATIM) else (GIT_CHECKOUT) configure_file(${VERSION_HPP_IN} ${VERSION_HPP}) endif (GIT_CHECKOUT) @@ -119,6 +119,10 @@ add_component_dir (fontloader fontloader ) +add_component_dir (gameplay + autocalc autocalcspell + ) + add_component_dir (version version ) @@ -161,14 +165,14 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) -target_link_libraries(components +target_link_libraries(components ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_WAVE_LIBRARY} ${OGRE_LIBRARIES} - ${OENGINE_LIBRARY} + ${OENGINE_LIBRARY} ${BULLET_LIBRARIES} ) diff --git a/components/gameplay/autocalc.cpp b/components/gameplay/autocalc.cpp new file mode 100644 index 000000000..a1888c434 --- /dev/null +++ b/components/gameplay/autocalc.cpp @@ -0,0 +1,207 @@ +#include "autocalc.hpp" + +#include +#include +#include +#include +#include +#include + +#include "autocalcspell.hpp" + +// Most of the code in this file was moved from apps/openmw/mwclass/npc.cpp +namespace +{ + int is_even(double d) + { + double int_part; + + modf(d / 2.0, &int_part); + return 2.0 * int_part == d; + } + + int round_ieee_754(double d) + { + double i = floor(d); + d -= i; + + if(d < 0.5) + return static_cast(i); + if(d > 0.5) + return static_cast(i) + 1; + if(is_even(i)) + return static_cast(i); + return static_cast(i) + 1; + } +} + +namespace GamePlay +{ + void autoCalcAttributesImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store) + { + // race bonus + bool male = (npc->mFlags & ESM::NPC::Female) == 0; + + for (int i=0; imData.mAttributeValues[i]; + stats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); + } + + // class bonus + for (int i=0; i<2; ++i) + { + int attribute = class_->mData.mAttribute[i]; + if (attribute>=0 && attributefindSkill(j); + + if (skill->mData.mAttribute != attribute) + continue; + + // is this a minor or major skill? + float add=0.2f; + for (int k=0; k<5; ++k) + { + if (class_->mData.mSkills[k][0] == j) + add=0.5; + } + for (int k=0; k<5; ++k) + { + if (class_->mData.mSkills[k][1] == j) + add=1.0; + } + modifierSum += add; + } + stats.setAttribute(attribute, + std::min(round_ieee_754(stats.getBaseAttribute(attribute) + (level-1) * modifierSum), 100) ); + } + } + + /** + * @brief autoCalculateSkills + * + * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ): + * + * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier) + * + * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill. + * + * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class, + * zero for other Skills. + * + * and by adding class, race, specialization bonus. + */ + void autoCalcSkillsImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store) + { + for (int i = 0; i < 2; ++i) + { + int bonus = (i==0) ? 10 : 25; + + for (int i2 = 0; i2 < 5; ++i2) + { + int index = class_->mData.mSkills[i2][i]; + if (index >= 0 && index < ESM::Skill::Length) + { + stats.setBaseSkill (index, stats.getBaseSkill(index) + bonus); + } + } + } + + for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) + { + float majorMultiplier = 0.1f; + float specMultiplier = 0.0f; + + int raceBonus = 0; + int specBonus = 0; + + for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) + { + if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) + { + raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; + break; + } + } + + for (int k = 0; k < 5; ++k) + { + // is this a minor or major skill? + if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) + { + majorMultiplier = 1.0f; + break; + } + } + + // is this skill in the same Specialization as the class? + const ESM::Skill* skill = store->findSkill(skillIndex); + if (skill->mData.mSpecialization == class_->mData.mSpecialization) + { + specMultiplier = 0.5f; + specBonus = 5; + } + + stats.setBaseSkill(skillIndex, + std::min( + round_ieee_754( + stats.getBaseSkill(skillIndex) + + 5 + + raceBonus + + specBonus + +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 + } + } + + unsigned short autoCalculateHealth(int level, const ESM::Class *class_, StatsBase& stats) + { + // initial health + int strength = stats.getBaseAttribute(ESM::Attribute::Strength); + int endurance = stats.getBaseAttribute(ESM::Attribute::Endurance); + + int multiplier = 3; + + if (class_->mData.mSpecialization == ESM::Class::Combat) + multiplier += 2; + else if (class_->mData.mSpecialization == ESM::Class::Stealth) + multiplier += 1; + + if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance + || class_->mData.mAttribute[1] == ESM::Attribute::Endurance) + multiplier += 1; + + return static_cast(floor(0.5f * (strength + endurance)) + multiplier * (level-1)); + } + + void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreWrap *store) + { + int skills[ESM::Skill::Length]; + for (int i=0; i spells = autoCalcNpcSpells(skills, attributes, race, store); + for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) + stats.addSpells(*it); + } + + StatsBase::StatsBase() {} + + StatsBase::~StatsBase() {} +} diff --git a/components/gameplay/autocalc.hpp b/components/gameplay/autocalc.hpp new file mode 100644 index 000000000..522070413 --- /dev/null +++ b/components/gameplay/autocalc.hpp @@ -0,0 +1,47 @@ +#ifndef COMPONENTS_GAMEPLAY_AUTOCALC_H +#define COMPONENTS_GAMEPLAY_AUTOCALC_H + +#include + +#include "store.hpp" + +namespace ESM +{ + struct NPC; + struct Race; + struct Class; +} + +namespace GamePlay +{ + // wrapper class for sharing the autocalc code between OpenMW and OpenCS + class StatsBase + { + + public: + + StatsBase(); + virtual ~StatsBase(); + + virtual unsigned char getBaseAttribute(int index) const = 0; + + virtual void setAttribute(int index, unsigned char value) = 0; + + virtual void addSpells(std::string id) = 0; + + virtual unsigned char getBaseSkill(int index) const = 0; + + virtual void setBaseSkill(int index, unsigned char value) = 0; + }; + + void autoCalcAttributesImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store); + + void autoCalcSkillsImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store); + + unsigned short autoCalculateHealth(int level, const ESM::Class *class_, StatsBase& stats); + + void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreWrap *store); +} +#endif // COMPONENTS_GAMEPLAY_AUTOCALC_H diff --git a/components/gameplay/autocalcspell.cpp b/components/gameplay/autocalcspell.cpp new file mode 100644 index 000000000..8f2843367 --- /dev/null +++ b/components/gameplay/autocalcspell.cpp @@ -0,0 +1,240 @@ +#include "autocalcspell.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "autocalc.hpp" + +// Most of the code in this file was moved from apps/openmw/mwmechanics/autocalcspell.cpp +namespace GamePlay +{ + + struct SchoolCaps + { + int mCount; + int mLimit; + bool mReachedLimit; + int mMinCost; + std::string mWeakestSpell; + }; + + std::vector autoCalcNpcSpells(const int *actorSkills, + const int *actorAttributes, const ESM::Race* race, StoreWrap *store) + { + static const float fNPCbaseMagickaMult = store->findGmstFloat("fNPCbaseMagickaMult"); + float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + static int iAutoSpellSchoolMax[6]; + static bool init = false; + if (!init) + { + for (int i=0; i<6; ++i) + { + const std::string& gmstName = "iAutoSpell" + schools[i] + "Max"; + iAutoSpellSchoolMax[i] = store->findGmstInt(gmstName); + } + init = true; + } + + std::map schoolCaps; + for (int i=0; i<6; ++i) + { + SchoolCaps caps; + caps.mCount = 0; + caps.mLimit = iAutoSpellSchoolMax[i]; + caps.mReachedLimit = iAutoSpellSchoolMax[i] <= 0; + caps.mMinCost = INT_MAX; + caps.mWeakestSpell.clear(); + schoolCaps[i] = caps; + } + + std::vector selectedSpells; + + const CommonStore &spells = store->getSpells(); + + // Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the + // Store must preserve the record ordering as it was in the content files. + for (CommonStore::iterator iter = spells.begin(); iter != spells.end(); ++iter) + { + const ESM::Spell* spell = &*iter; + + if (spell->mData.mType != ESM::Spell::ST_Spell) + continue; + if (!(spell->mData.mFlags & ESM::Spell::F_Autocalc)) + continue; + static const int iAutoSpellTimesCanCast = store->findGmstInt("iAutoSpellTimesCanCast"); + if (baseMagicka < iAutoSpellTimesCanCast * spell->mData.mCost) + continue; + + if (race && race->mPowers.exists(spell->mId)) + continue; + + if (!attrSkillCheck(spell, actorSkills, actorAttributes, store)) + continue; + + int school; + float skillTerm; + calcWeakestSchool(spell, actorSkills, school, skillTerm, store); + assert(school >= 0 && school < 6); + SchoolCaps& cap = schoolCaps[school]; + + if (cap.mReachedLimit && spell->mData.mCost <= cap.mMinCost) + continue; + + static const float fAutoSpellChance = store->findGmstFloat("fAutoSpellChance"); + if (calcAutoCastChance(spell, actorSkills, actorAttributes, school, store) < fAutoSpellChance) + continue; + + selectedSpells.push_back(spell->mId); + + if (cap.mReachedLimit) + { + std::vector::iterator found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell); + if (found != selectedSpells.end()) + selectedSpells.erase(found); + + cap.mMinCost = INT_MAX; + for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) + { + const ESM::Spell* testSpell = spells.find(*weakIt); + + //int testSchool; + //float dummySkillTerm; + //calcWeakestSchool(testSpell, actorSkills, testSchool, dummySkillTerm); + + // Note: if there are multiple spells with the same cost, we pick the first one we found. + // So the algorithm depends on the iteration order of the outer loop. + if ( + // There is a huge bug here. It is not checked that weakestSpell is of the correct school. + // As result multiple SchoolCaps could have the same mWeakestSpell. Erasing the weakest spell would then fail if another school + // already erased it, and so the number of spells would often exceed the sum of limits. + // This bug cannot be fixed without significantly changing the results of the spell autocalc, which will not have been playtested. + //testSchool == school && + testSpell->mData.mCost < cap.mMinCost) + { + cap.mMinCost = testSpell->mData.mCost; + cap.mWeakestSpell = testSpell->mId; + } + } + } + else + { + cap.mCount += 1; + if (cap.mCount == cap.mLimit) + cap.mReachedLimit = true; + + if (spell->mData.mCost < cap.mMinCost) + { + cap.mWeakestSpell = spell->mId; + cap.mMinCost = spell->mData.mCost; + } + } + } + + return selectedSpells; + } + + bool attrSkillCheck (const ESM::Spell* spell, + const int* actorSkills, const int* actorAttributes, StoreWrap *store) + { + const std::vector& effects = spell->mEffects.mList; + for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) + { + const ESM::MagicEffect* magicEffect = store->findMagicEffect(effectIt->mEffectID); + static const int iAutoSpellAttSkillMin = store->findGmstInt("iAutoSpellAttSkillMin"); + + if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) + { + assert (effectIt->mSkill >= 0 && effectIt->mSkill < ESM::Skill::Length); + if (actorSkills[effectIt->mSkill] < iAutoSpellAttSkillMin) + return false; + } + + if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)) + { + assert (effectIt->mAttribute >= 0 && effectIt->mAttribute < ESM::Attribute::Length); + if (actorAttributes[effectIt->mAttribute] < iAutoSpellAttSkillMin) + return false; + } + } + + return true; + } + + ESM::Skill::SkillEnum mapSchoolToSkill(int school) + { + std::map schoolSkillMap; // maps spell school to skill id + schoolSkillMap[0] = ESM::Skill::Alteration; + schoolSkillMap[1] = ESM::Skill::Conjuration; + schoolSkillMap[3] = ESM::Skill::Illusion; + schoolSkillMap[2] = ESM::Skill::Destruction; + schoolSkillMap[4] = ESM::Skill::Mysticism; + schoolSkillMap[5] = ESM::Skill::Restoration; + assert(schoolSkillMap.find(school) != schoolSkillMap.end()); + return schoolSkillMap[school]; + } + + void calcWeakestSchool (const ESM::Spell* spell, + const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreWrap *store) + { + float minChance = FLT_MAX; + + const ESM::EffectList& effects = spell->mEffects; + for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) + { + const ESM::ENAMstruct& effect = *it; + float x = static_cast(effect.mDuration); + + const ESM::MagicEffect* magicEffect = store->findMagicEffect(effect.mEffectID); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) + x = std::max(1.f, x); + + x *= 0.1f * magicEffect->mData.mBaseCost; + x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); + x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; + if (effect.mRange == ESM::RT_Target) + x *= 1.5f; + + static const float fEffectCostMult = store->findGmstFloat("fEffectCostMult"); + x *= fEffectCostMult; + + float s = 2.f * actorSkills[mapSchoolToSkill(magicEffect->mData.mSchool)]; + if (s - x < minChance) + { + minChance = s - x; + effectiveSchool = magicEffect->mData.mSchool; + skillTerm = s; + } + } + } + + float calcAutoCastChance(const ESM::Spell *spell, + const int *actorSkills, const int *actorAttributes, int effectiveSchool, StoreWrap *store) + { + if (spell->mData.mType != ESM::Spell::ST_Spell) + return 100.f; + + if (spell->mData.mFlags & ESM::Spell::F_Always) + return 100.f; + + float skillTerm = 0; + if (effectiveSchool != -1) + skillTerm = 2.f * actorSkills[mapSchoolToSkill(effectiveSchool)]; + else + calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm, store); // Note effectiveSchool is unused after this + + float castChance = skillTerm - spell->mData.mCost + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck]; + return castChance; + } +} diff --git a/components/gameplay/autocalcspell.hpp b/components/gameplay/autocalcspell.hpp new file mode 100644 index 000000000..ceb9f197b --- /dev/null +++ b/components/gameplay/autocalcspell.hpp @@ -0,0 +1,38 @@ +#ifndef COMPONENTS_GAMEPLAY_AUTOCALCSPELL_H +#define COMPONENTS_GAMEPLAY_AUTOCALCSPELL_H + +#include + +#include + +namespace ESM +{ + struct Spell; + struct Race; +} + +namespace GamePlay +{ + +class StoreWrap; + +/// Contains algorithm for calculating an NPC's spells based on stats + +std::vector autoCalcNpcSpells(const int* actorSkills, + const int* actorAttributes, const ESM::Race* race, StoreWrap *store); + +// Helpers + +bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, StoreWrap *store); + +ESM::Skill::SkillEnum mapSchoolToSkill(int school); + +void calcWeakestSchool(const ESM::Spell* spell, + const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreWrap *store); + +float calcAutoCastChance(const ESM::Spell* spell, + const int* actorSkills, const int* actorAttributes, int effectiveSchool, StoreWrap *store); + +} + +#endif diff --git a/components/gameplay/store.hpp b/components/gameplay/store.hpp new file mode 100644 index 000000000..c4d7cba17 --- /dev/null +++ b/components/gameplay/store.hpp @@ -0,0 +1,138 @@ +#ifndef COMPONENTS_GAMEPLAY_STORE_H +#define COMPONENTS_GAMEPLAY_STORE_H + +#include +#include + +namespace Loading +{ + class Listener; +} + +namespace ESM +{ + class ESMWriter; + class ESMReader; + struct Spell; + struct Skill; + struct MagicEffect; +} + +namespace GamePlay +{ + // moved from apps/openmw/mwworld/store.hpp + struct StoreBase + { + virtual ~StoreBase() {} + + virtual void setUp() {} + virtual void listIdentifier(std::vector &list) const {} + + virtual size_t getSize() const = 0; + virtual int getDynamicSize() const { return 0; } + virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; + + virtual bool eraseStatic(const std::string &id) {return false;} + virtual void clearDynamic() {} + + virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} + + virtual void read (ESM::ESMReader& reader, const std::string& id) {} + ///< Read into dynamic storage + }; + + // moved from apps/openmw/mwworld/store.hpp + template + class SharedIterator + { + typedef typename std::vector::const_iterator Iter; + + Iter mIter; + + public: + SharedIterator() {} + + SharedIterator(const SharedIterator &orig) + : mIter(orig.mIter) + {} + + SharedIterator(const Iter &iter) + : mIter(iter) + {} + + SharedIterator &operator++() { + ++mIter; + return *this; + } + + SharedIterator operator++(int) { + SharedIterator iter = *this; + ++mIter; + + return iter; + } + + SharedIterator &operator--() { + --mIter; + return *this; + } + + SharedIterator operator--(int) { + SharedIterator iter = *this; + --mIter; + + return iter; + } + + bool operator==(const SharedIterator &x) const { + return mIter == x.mIter; + } + + bool operator!=(const SharedIterator &x) const { + return !(*this == x); + } + + const T &operator*() const { + return **mIter; + } + + const T *operator->() const { + return &(**mIter); + } + }; + + // interface class for sharing the autocalc component between OpenMW and OpenCS + template + class CommonStore : public StoreBase + { + + public: + typedef SharedIterator iterator; + + virtual iterator begin() const = 0; + + virtual iterator end() const = 0; + + virtual const T *find(const std::string &id) const = 0; + }; + + // interface class for sharing the autocalc component between OpenMW and OpenCS + class StoreWrap + { + + public: + StoreWrap() {} + virtual ~StoreWrap() {} + + virtual int findGmstInt(const std::string& gmst) const = 0; + + virtual float findGmstFloat(const std::string& gmst) const = 0; + + virtual const ESM::Skill *findSkill(int index) const = 0; + + virtual const ESM::MagicEffect* findMagicEffect(int id) const = 0; + + virtual const CommonStore& getSpells() const = 0; + }; +} +#endif // COMPONENTS_GAMEPLAY_STORE_H From 18ec0d3e656be71d7e563c1cfe9761a80d0efab3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 24 Jun 2015 21:21:35 +1000 Subject: [PATCH 040/441] Add missing files for autocalc. --- apps/opencs/model/world/npcstats.cpp | 138 +++++++++++++++++++++++++++ apps/opencs/model/world/npcstats.hpp | 74 ++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 apps/opencs/model/world/npcstats.cpp create mode 100644 apps/opencs/model/world/npcstats.hpp diff --git a/apps/opencs/model/world/npcstats.cpp b/apps/opencs/model/world/npcstats.cpp new file mode 100644 index 000000000..da14b473e --- /dev/null +++ b/apps/opencs/model/world/npcstats.cpp @@ -0,0 +1,138 @@ +#include "npcstats.hpp" + +#include +#include + +namespace CSMWorld +{ + NpcStats::NpcStats() : mHealth(0), mMana(0), mFatigue(0) + { + for (int i = 0; i < ESM::Skill::Length; ++i) + mSkill[i] = 0; + } + + NpcStats::NpcStats(const NpcStats &other) + { + for (int i = 0; i < ESM::Attribute::Length; ++i) + mAttr[i] = other.mAttr[i]; + + mSpells = other.mSpells; + + for (int i = 0; i < ESM::Skill::Length; ++i) + mSkill[i] = 0; + + mHealth = other.mHealth; + mMana = other.mMana; + mFatigue = other.mFatigue; + } + + NpcStats::~NpcStats() + {} + + unsigned char NpcStats::getBaseAttribute(int index) const + { + if (index < 0 || index >= ESM::Attribute::Length) + throw std::runtime_error("attrib index out of bounds"); + + return mAttr[index]; + } + + void NpcStats::setAttribute(int index, unsigned char value) + { + if (index < 0 || index >= ESM::Attribute::Length) + throw std::runtime_error("attrib index out of bounds"); + + mAttr[index] = value; + } + + void NpcStats::addSpells(std::string id) + { + struct SpellInfo info; + info.mName = id; + info.mType = ESM::Spell::ST_Spell; // default type from autocalc + info.mFromRace = false; + info.mCost = 0; + info.mChance = 0; + + mSpells.insert(mSpells.begin(), info); + } + + void NpcStats::addPowers(const std::string& id, int type) + { + struct SpellInfo info; + info.mName = id; + info.mType = type; + info.mFromRace = true; + info.mCost = 0; + info.mChance = 0; + + mSpells.push_back(info); + } + + void NpcStats::addCostAndChance(const std::string& id, int cost, int chance) + { + // usually only a few spells, so simply iterate through rather than keeping a separate + // lookup index or map + for (std::vector::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + if ((*it).mName == id) + { + (*it).mCost = cost; + (*it).mChance = chance; + return; + } + } + } + + const std::vector& NpcStats::spells() const + { + return mSpells; + } + + unsigned char NpcStats::getBaseSkill(int index) const + { + if (index < 0 || index >= ESM::Skill::Length) + throw std::runtime_error("skill index out of bounds"); + + return mSkill[index]; + } + + void NpcStats::setBaseSkill(int index, unsigned char value) + { + if (index < 0 || index >= ESM::Skill::Length) + throw std::runtime_error("skill index out of bounds"); + + mSkill[index] = value; + } + + unsigned short NpcStats::getHealth() + { + return mHealth; + } + + void NpcStats::setHealth(unsigned short health) + { + mHealth = health; + } + + unsigned short NpcStats::getMana() + { + return mMana; + } + + void NpcStats::setMana(unsigned short mana) + { + mMana = mana; + } + + unsigned short NpcStats::getFatigue() + { + return mFatigue; + } + + void NpcStats::setFatigue(unsigned short fatigue) + { + mFatigue = fatigue; + } +} + diff --git a/apps/opencs/model/world/npcstats.hpp b/apps/opencs/model/world/npcstats.hpp new file mode 100644 index 000000000..a020e0499 --- /dev/null +++ b/apps/opencs/model/world/npcstats.hpp @@ -0,0 +1,74 @@ +#ifndef CSM_WORLD_NPCSTATS_H +#define CSM_WORLD_NPCSTATS_H + +#include + +#include + +#include +#include +#include + +namespace CSMWorld +{ + struct SpellInfo + { + std::string mName; + int mType; + bool mFromRace; + int mCost; + int mChance; + }; + + class NpcStats : public GamePlay::StatsBase + { + + int mAttr[ESM::Attribute::Length]; + std::vector mSpells; + int mSkill[ESM::Skill::Length]; + + unsigned short mHealth; + unsigned short mMana; + unsigned short mFatigue; + + public: + + NpcStats(); + + NpcStats(const NpcStats &other); + + ~NpcStats(); + + virtual unsigned char getBaseAttribute(int index) const; + + virtual void setAttribute(int index, unsigned char value); + + virtual void addSpells(std::string id); + + void addPowers(const std::string& id, int type); + + void addCostAndChance(const std::string& id, int cost, int chance); + + const std::vector& spells() const; + + virtual unsigned char getBaseSkill(int index) const; + + virtual void setBaseSkill(int index, unsigned char value); + + unsigned short getHealth(); + + void setHealth(unsigned short health); + + unsigned short getMana(); + + void setMana(unsigned short mana); + + unsigned short getFatigue(); + + void setFatigue(unsigned short fatigue); + }; +} + +Q_DECLARE_METATYPE(CSMWorld::NpcStats*); + +#endif // CSM_WORLD_NPCSTATS_H From 91b9ad399fbd0571fbd03b6779de4aed851703e3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 24 Jun 2015 21:38:38 +1000 Subject: [PATCH 041/441] Include for fmod() and floor() --- components/gameplay/autocalc.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/gameplay/autocalc.cpp b/components/gameplay/autocalc.cpp index a1888c434..fbfc4a020 100644 --- a/components/gameplay/autocalc.cpp +++ b/components/gameplay/autocalc.cpp @@ -1,5 +1,7 @@ #include "autocalc.hpp" +#include + #include #include #include From 0cf98320179f0a6324b91a5c954ddb46f499bf22 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 24 Jun 2015 21:58:28 +1000 Subject: [PATCH 042/441] Add missing files for autocalc. Remove c++11 dependency. --- apps/opencs/model/world/npcstats.hpp | 2 +- apps/opencs/model/world/refidadapterimp.cpp | 6 ++++ apps/opencs/model/world/usertype.hpp | 4 +-- apps/openmw/mwworld/mwstore.cpp | 36 +++++++++++++++++++++ apps/openmw/mwworld/mwstore.hpp | 34 +++++++++++++++++++ 5 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 apps/openmw/mwworld/mwstore.cpp create mode 100644 apps/openmw/mwworld/mwstore.hpp diff --git a/apps/opencs/model/world/npcstats.hpp b/apps/opencs/model/world/npcstats.hpp index a020e0499..a5287d2b4 100644 --- a/apps/opencs/model/world/npcstats.hpp +++ b/apps/opencs/model/world/npcstats.hpp @@ -69,6 +69,6 @@ namespace CSMWorld }; } -Q_DECLARE_METATYPE(CSMWorld::NpcStats*); +Q_DECLARE_METATYPE(CSMWorld::NpcStats*) #endif // CSM_WORLD_NPCSTATS_H diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 085c77ca3..56c4835f8 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -1122,6 +1122,7 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData } } +template <> void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { @@ -1145,6 +1146,7 @@ void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColum record.setModified (caster); } +template <> void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { @@ -1175,6 +1177,7 @@ void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdCo record.setModified (caster); } +template <> void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { @@ -1207,6 +1210,7 @@ void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColu record.setModified (caster); } +template <> QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { @@ -1230,12 +1234,14 @@ QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefId } } +template <> int CSMWorld::NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 5; } +template <> int CSMWorld::NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = diff --git a/apps/opencs/model/world/usertype.hpp b/apps/opencs/model/world/usertype.hpp index e0b3c2e2f..23b5c90a4 100644 --- a/apps/opencs/model/world/usertype.hpp +++ b/apps/opencs/model/world/usertype.hpp @@ -38,7 +38,7 @@ namespace CSMWorld }; } -Q_DECLARE_METATYPE(CSMWorld::UserInt); -Q_DECLARE_METATYPE(CSMWorld::UserFloat); +Q_DECLARE_METATYPE(CSMWorld::UserInt) +Q_DECLARE_METATYPE(CSMWorld::UserFloat) #endif // CSM_WORLD_USERTYPE_H diff --git a/apps/openmw/mwworld/mwstore.cpp b/apps/openmw/mwworld/mwstore.cpp new file mode 100644 index 000000000..02c7106b6 --- /dev/null +++ b/apps/openmw/mwworld/mwstore.cpp @@ -0,0 +1,36 @@ +#include "mwstore.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "esmstore.hpp" + +namespace MWWorld +{ + MWStore::MWStore() + : mGmst(MWBase::Environment::get().getWorld()->getStore().get()), + mSpells(MWBase::Environment::get().getWorld()->getStore().get()) + { } + + MWStore::~MWStore() + { } + + int MWStore::findGmstInt(const std::string& name) const { return mGmst.find(name)->getInt(); } + + float MWStore::findGmstFloat(const std::string& name) const { return mGmst.find(name)->getFloat(); } + + const ESM::Skill *MWStore::findSkill(int index) const + { + return MWBase::Environment::get().getWorld()->getStore().get().find(index); + } + + const ESM::MagicEffect* MWStore::findMagicEffect(int id) const + { + return MWBase::Environment::get().getWorld()->getStore().get().find(id); + } + + const GamePlay::CommonStore& MWStore::getSpells() const + { + return MWBase::Environment::get().getWorld()->getStore().get(); + } +} diff --git a/apps/openmw/mwworld/mwstore.hpp b/apps/openmw/mwworld/mwstore.hpp new file mode 100644 index 000000000..c616e4e1d --- /dev/null +++ b/apps/openmw/mwworld/mwstore.hpp @@ -0,0 +1,34 @@ +#ifndef GAME_MWWORLD_MWSTORE_H +#define GAME_MWWORLD_MWSTORE_H + +#include + +#include + +#include "store.hpp" + +namespace MWWorld +{ + class MWStore : public GamePlay::StoreWrap + { + const MWWorld::Store& mGmst; + const MWWorld::Store &mSpells; + + public: + + MWStore(); + ~MWStore(); + + virtual int findGmstInt(const std::string& name) const; + + virtual float findGmstFloat(const std::string& name) const; + + virtual const ESM::Skill *findSkill(int index) const; + + virtual const ESM::MagicEffect* findMagicEffect(int id) const; + + virtual const GamePlay::CommonStore& getSpells() const; + }; +} + +#endif // GAME_MWWORLD_MWSTORE_H From e5e4a04f8b3cf77147ab81dceb7947a821520160 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 24 Jun 2015 22:19:27 +1000 Subject: [PATCH 043/441] Remove more c++11 dependencies. --- apps/opencs/model/world/refidadapterimp.cpp | 33 ++++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 56c4835f8..99ca76d73 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -1122,8 +1122,11 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData } } +namespace CSMWorld +{ + template <> -void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, +void NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { Record& record = @@ -1147,7 +1150,7 @@ void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColum } template <> -void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, +void NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { Record& record = @@ -1178,7 +1181,7 @@ void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdCo } template <> -void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, +void NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { Record& record = @@ -1211,13 +1214,13 @@ void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColu } template <> -QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, +QVariant NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - const std::vector& spells = mData.npcAutoCalculate(record.get())->spells(); + const std::vector& spells = mData.npcAutoCalculate(record.get())->spells(); if (subRowIndex < 0 || subRowIndex >= static_cast (spells.size())) throw std::runtime_error ("index out of range"); @@ -1235,24 +1238,24 @@ QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefId } template <> -int CSMWorld::NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, +int NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 5; } template <> -int CSMWorld::NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +int NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - const std::vector spells = mData.npcAutoCalculate(record.get())->spells(); + const std::vector spells = mData.npcAutoCalculate(record.get())->spells(); return static_cast(spells.size()); } template <> -void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, +void NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { Record& record = @@ -1272,7 +1275,7 @@ void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefId } template <> -void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, +void NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { Record& record = @@ -1290,7 +1293,7 @@ void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const Re } template <> -void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, +void NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { Record& record = @@ -1310,7 +1313,7 @@ void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefI } template<> -QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, +QVariant NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { const Record& record = @@ -1337,17 +1340,19 @@ QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const } template <> -int CSMWorld::NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, +int NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 2; } template <> -int CSMWorld::NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +int NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); return static_cast(record.get().mSpells.mList.size()); } + +} From 7af43a115584fa173b696ba6f1d8dd5f843b19c4 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 25 Jun 2015 13:32:22 +1000 Subject: [PATCH 044/441] Address review feedback. --- apps/opencs/model/world/data.cpp | 81 +++------- apps/opencs/model/world/npcstats.hpp | 4 +- apps/openmw/mwclass/npc.cpp | 35 ++--- apps/openmw/mwgui/levelupdialog.cpp | 4 +- .../mwmechanics/mechanicsmanagerimp.cpp | 6 +- apps/openmw/mwworld/esmstore.cpp | 4 +- apps/openmw/mwworld/esmstore.hpp | 6 +- apps/openmw/mwworld/mwstore.cpp | 4 +- apps/openmw/mwworld/mwstore.hpp | 6 +- apps/openmw/mwworld/store.hpp | 100 +++++++++++-- components/CMakeLists.txt | 22 +-- .../{gameplay => autocalc}/autocalc.cpp | 8 +- .../{gameplay => autocalc}/autocalc.hpp | 14 +- .../{gameplay => autocalc}/autocalcspell.cpp | 28 ++-- .../{gameplay => autocalc}/autocalcspell.hpp | 19 +-- components/autocalc/store.hpp | 42 ++++++ components/gameplay/store.hpp | 138 ------------------ 17 files changed, 236 insertions(+), 285 deletions(-) rename components/{gameplay => autocalc}/autocalc.cpp (98%) rename components/{gameplay => autocalc}/autocalc.hpp (79%) rename components/{gameplay => autocalc}/autocalcspell.cpp (92%) rename components/{gameplay => autocalc}/autocalcspell.hpp (61%) create mode 100644 components/autocalc/store.hpp delete mode 100644 components/gameplay/store.hpp diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 5b4f43ad3..b789bd28f 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -11,9 +11,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "idtable.hpp" #include "idtree.hpp" @@ -27,15 +27,21 @@ namespace { - class SpellStore : public GamePlay::CommonStore + class CSStore : public AutoCalc::StoreCommon { + const CSMWorld::IdCollection& mGmstTable; + const CSMWorld::IdCollection& mSkillTable; + const CSMWorld::IdCollection& mMagicEffectTable; const CSMWorld::NestedIdCollection& mSpells; std::vector mLocal; public: - SpellStore(const CSMWorld::NestedIdCollection& spells) - : mSpells(spells) + CSStore(const CSMWorld::IdCollection& gmst, + const CSMWorld::IdCollection& skills, + const CSMWorld::IdCollection& magicEffects, + const CSMWorld::NestedIdCollection& spells) + : mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpells(spells) { // prepare data in a format used by OpenMW store for (int index = 0; index < mSpells.getSize(); ++index) @@ -44,53 +50,6 @@ namespace mLocal.push_back(spell); } } - - ~SpellStore() {} - - typedef GamePlay::SharedIterator iterator; - - virtual iterator begin() const - { - return mLocal.begin(); - } - - virtual iterator end() const - { - return mLocal.end(); - } - - virtual const ESM::Spell *find(const std::string &id) const - { - return &mSpells.getRecord(id).get(); - } - - virtual size_t getSize() const - { - return mSpells.getSize(); - } - - private: - // not used in OpenCS - virtual void load(ESM::ESMReader &esm, const std::string &id) - { - } - }; - - class CSStore : public GamePlay::StoreWrap - { - const CSMWorld::IdCollection& mGmstTable; - const CSMWorld::IdCollection& mSkillTable; - const CSMWorld::IdCollection& mMagicEffectTable; - const SpellStore mSpellStore; - - public: - - CSStore(const CSMWorld::IdCollection& gmst, - const CSMWorld::IdCollection& skills, - const CSMWorld::IdCollection& magicEffects, - const CSMWorld::NestedIdCollection& spells) - : mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpellStore(spells) - { } ~CSStore() {} virtual int findGmstInt(const std::string& name) const @@ -115,18 +74,18 @@ namespace return &mMagicEffectTable.getRecord(ESM::MagicEffect::indexToId((short)id)).get(); } - virtual const GamePlay::CommonStore& getSpells() const + virtual const std::vector& getSpells() const { - return mSpellStore; + return mLocal; } }; - unsigned short autoCalculateMana(GamePlay::StatsBase& stats) + unsigned short autoCalculateMana(AutoCalc::StatsBase& stats) { return stats.getBaseAttribute(ESM::Attribute::Intelligence) * 2; } - unsigned short autoCalculateFatigue(GamePlay::StatsBase& stats) + unsigned short autoCalculateFatigue(AutoCalc::StatsBase& stats) { return stats.getBaseAttribute(ESM::Attribute::Strength) + stats.getBaseAttribute(ESM::Attribute::Willpower) @@ -1579,15 +1538,15 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const if (autoCalc) { - GamePlay::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store); + AutoCalc::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store); stats->setHealth(autoCalculateHealth(level, class_, *stats)); stats->setMana(autoCalculateMana(*stats)); stats->setFatigue(autoCalculateFatigue(*stats)); - GamePlay::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store); + AutoCalc::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store); - GamePlay::autoCalculateSpells(race, *stats, &store); + AutoCalc::autoCalculateSpells(race, *stats, &store); } else { @@ -1645,7 +1604,7 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const int school; float skillTerm; - GamePlay::calcWeakestSchool(spell, skills, school, skillTerm, &store); + AutoCalc::calcWeakestSchool(spell, skills, school, skillTerm, &store); float chance = calcAutoCastChance(spell, skills, attributes, school, &store); stats->addCostAndChance((*it).mName, cost, (int)ceil(chance)); // percent diff --git a/apps/opencs/model/world/npcstats.hpp b/apps/opencs/model/world/npcstats.hpp index a5287d2b4..4d9be149f 100644 --- a/apps/opencs/model/world/npcstats.hpp +++ b/apps/opencs/model/world/npcstats.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include namespace CSMWorld { @@ -20,7 +20,7 @@ namespace CSMWorld int mChance; }; - class NpcStats : public GamePlay::StatsBase + class NpcStats : public AutoCalc::StatsBase { int mAttr[ESM::Attribute::Length]; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4f419c635..64ed868a1 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -11,9 +11,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -62,28 +62,26 @@ namespace return new NpcCustomData (*this); } - class Stats : public GamePlay::StatsBase + class Stats : public AutoCalc::StatsBase { - MWMechanics::CreatureStats& mCreatureStats; MWMechanics::NpcStats& mNpcStats; public: - Stats(MWMechanics::CreatureStats& creatureStats, MWMechanics::NpcStats& npcStats) - : mCreatureStats(creatureStats), mNpcStats(npcStats) {} + Stats(MWMechanics::NpcStats& npcStats) : mNpcStats(npcStats) {} - virtual unsigned char getBaseAttribute(int index) const { return mCreatureStats.getAttribute(index).getBase(); } + virtual unsigned char getBaseAttribute(int index) const { return mNpcStats.getAttribute(index).getBase(); } - virtual void setAttribute(int index, unsigned char value) { mCreatureStats.setAttribute(index, value); } + virtual void setAttribute(int index, unsigned char value) { mNpcStats.setAttribute(index, value); } - virtual void addSpells(std::string id) { mCreatureStats.getSpells().add(id); } + virtual void addSpells(std::string id) { mNpcStats.getSpells().add(id); } virtual unsigned char getBaseSkill(int index) const { return mNpcStats.getSkill(index).getBase(); } virtual void setBaseSkill(int index, unsigned char value) { mNpcStats.getSkill(index).setBase(value); } }; - void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) + void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::NpcStats& npcStats) { const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); @@ -91,16 +89,15 @@ namespace const ESM::Class *class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); - int level = creatureStats.getLevel(); + int level = npcStats.getLevel(); - MWMechanics::NpcStats dummy; // npc stats are needed for skills, which is not calculated here - Stats stats(creatureStats, dummy); + Stats stats(npcStats); MWWorld::MWStore store; - GamePlay::autoCalcAttributesImpl (npc, race, class_, level, stats, &store); + AutoCalc::autoCalcAttributesImpl (npc, race, class_, level, stats, &store); - creatureStats.setHealth(GamePlay::autoCalculateHealth(level, class_, stats)); + npcStats.setHealth(AutoCalc::autoCalculateHealth(level, class_, stats)); } void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr) @@ -112,13 +109,13 @@ namespace const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); - Stats stats(npcStats, npcStats); + Stats stats(npcStats); MWWorld::MWStore store; - GamePlay::autoCalcSkillsImpl(npc, race, class_, level, stats, &store); + AutoCalc::autoCalcSkillsImpl(npc, race, class_, level, stats, &store); - GamePlay::autoCalculateSpells(race, stats, &store); + AutoCalc::autoCalculateSpells(race, stats, &store); } } diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index c6954d91d..c392372ff 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -142,7 +140,7 @@ namespace MWGui if(world->getStore().get().isDynamic(cls->mId)) { // Choosing Stealth specialization and Speed/Agility as attributes, if possible. Otherwise fall back to first class found. - GamePlay::SharedIterator it = world->getStore().get().begin(); + MWWorld::SharedIterator it = world->getStore().get().begin(); for(; it != world->getStore().get().end(); ++it) { if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 5d61bc1d2..dc388555e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -6,7 +6,7 @@ #include -#include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" @@ -256,10 +256,10 @@ namespace MWMechanics static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->getFloat(); MWWorld::MWStore store; - if (GamePlay::calcAutoCastChance(spell, skills, attributes, -1, &store) < fAutoPCSpellChance) + if (AutoCalc::calcAutoCastChance(spell, skills, attributes, -1, &store) < fAutoPCSpellChance) continue; - if (!GamePlay::attrSkillCheck(spell, skills, attributes, &store)) + if (!AutoCalc::attrSkillCheck(spell, skills, attributes, &store)) continue; selectedSpells.push_back(spell->mId); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 61518548b..5d9beecb6 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -66,7 +66,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) esm.getRecHeader(); // Look up the record type. - std::map::iterator it = mStores.find(n.val); + std::map::iterator it = mStores.find(n.val); if (it == mStores.end()) { if (n.val == ESM::REC_INFO) { @@ -130,7 +130,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) void ESMStore::setUp() { - std::map::iterator it = mStores.begin(); + std::map::iterator it = mStores.begin(); for (; it != mStores.end(); ++it) { it->second->setUp(); } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 61d361b3e..05b633956 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -67,7 +67,7 @@ namespace MWWorld // Lookup of all IDs. Makes looking up references faster. Just // maps the id name to the record type. std::map mIds; - std::map mStores; + std::map mStores; ESM::NPC mPlayerTemplate; @@ -75,7 +75,7 @@ namespace MWWorld public: /// \todo replace with SharedIterator - typedef std::map::const_iterator iterator; + typedef std::map::const_iterator iterator; iterator begin() const { return mStores.begin(); @@ -144,7 +144,7 @@ namespace MWWorld void clearDynamic () { - for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) + for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) it->second->clearDynamic(); mNpcs.insert(mPlayerTemplate); diff --git a/apps/openmw/mwworld/mwstore.cpp b/apps/openmw/mwworld/mwstore.cpp index 02c7106b6..8ebe91cd9 100644 --- a/apps/openmw/mwworld/mwstore.cpp +++ b/apps/openmw/mwworld/mwstore.cpp @@ -29,8 +29,8 @@ namespace MWWorld return MWBase::Environment::get().getWorld()->getStore().get().find(id); } - const GamePlay::CommonStore& MWStore::getSpells() const + const std::vector& MWStore::getSpells() const { - return MWBase::Environment::get().getWorld()->getStore().get(); + return MWBase::Environment::get().getWorld()->getStore().get().getShared(); } } diff --git a/apps/openmw/mwworld/mwstore.hpp b/apps/openmw/mwworld/mwstore.hpp index c616e4e1d..c43c58931 100644 --- a/apps/openmw/mwworld/mwstore.hpp +++ b/apps/openmw/mwworld/mwstore.hpp @@ -3,13 +3,13 @@ #include -#include +#include #include "store.hpp" namespace MWWorld { - class MWStore : public GamePlay::StoreWrap + class MWStore : public AutoCalc::StoreCommon { const MWWorld::Store& mGmst; const MWWorld::Store &mSpells; @@ -27,7 +27,7 @@ namespace MWWorld virtual const ESM::MagicEffect* findMagicEffect(int id) const; - virtual const GamePlay::CommonStore& getSpells() const; + virtual const std::vector& getSpells() const; }; } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 852092a9a..c86a92f7a 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -10,7 +10,6 @@ #include #include -#include #include @@ -18,10 +17,89 @@ namespace MWWorld { + struct StoreBase + { + virtual ~StoreBase() {} + + virtual void setUp() {} + virtual void listIdentifier(std::vector &list) const {} + + virtual size_t getSize() const = 0; + virtual int getDynamicSize() const { return 0; } + virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; + + virtual bool eraseStatic(const std::string &id) {return false;} + virtual void clearDynamic() {} + + virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} + + virtual void read (ESM::ESMReader& reader, const std::string& id) {} + ///< Read into dynamic storage + }; + + template + class SharedIterator + { + typedef typename std::vector::const_iterator Iter; + + Iter mIter; + + public: + SharedIterator() {} + + SharedIterator(const SharedIterator &orig) + : mIter(orig.mIter) + {} + + SharedIterator(const Iter &iter) + : mIter(iter) + {} + + SharedIterator &operator++() { + ++mIter; + return *this; + } + + SharedIterator operator++(int) { + SharedIterator iter = *this; + ++mIter; + + return iter; + } + + SharedIterator &operator--() { + --mIter; + return *this; + } + + SharedIterator operator--(int) { + SharedIterator iter = *this; + --mIter; + + return iter; + } + + bool operator==(const SharedIterator &x) const { + return mIter == x.mIter; + } + + bool operator!=(const SharedIterator &x) const { + return !(*this == x); + } + + const T &operator*() const { + return **mIter; + } + + const T *operator->() const { + return &(**mIter); + } + }; + class ESMStore; template - class Store : public GamePlay::CommonStore + class Store : public StoreBase { std::map mStatic; std::vector mShared; // Preserves the record order as it came from the content files (this @@ -59,7 +137,7 @@ namespace MWWorld : mStatic(orig.mData) {} - typedef GamePlay::SharedIterator iterator; + typedef SharedIterator iterator; // setUp needs to be called again after virtual void clearDynamic() @@ -156,6 +234,10 @@ namespace MWWorld return mShared.size(); } + const std::vector& getShared() const { + return mShared; + } + int getDynamicSize() const { return static_cast (mDynamic.size()); // truncated from unsigned __int64 if _MSC_VER && _WIN64 @@ -302,7 +384,7 @@ namespace MWWorld } template <> - class Store : public GamePlay::StoreBase + class Store : public StoreBase { // For multiple ESM/ESP files we need one list per file. typedef std::vector LandTextureList; @@ -379,7 +461,7 @@ namespace MWWorld }; template <> - class Store : public GamePlay::StoreBase + class Store : public StoreBase { std::vector mStatic; @@ -394,7 +476,7 @@ namespace MWWorld }; public: - typedef GamePlay::SharedIterator iterator; + typedef SharedIterator iterator; virtual ~Store() { @@ -468,7 +550,7 @@ namespace MWWorld }; template <> - class Store : public GamePlay::StoreBase + class Store : public StoreBase { struct DynamicExtCmp { @@ -508,7 +590,7 @@ namespace MWWorld void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); public: - typedef GamePlay::SharedIterator iterator; + typedef SharedIterator iterator; const ESM::Cell *search(const std::string &id) const { ESM::Cell cell; @@ -756,7 +838,7 @@ namespace MWWorld }; template <> - class Store : public GamePlay::StoreBase + class Store : public StoreBase { private: typedef std::map Interior; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 8c37c8ef3..2938dcd81 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -6,16 +6,16 @@ set (VERSION_HPP ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp) if (GIT_CHECKOUT) add_custom_target (git-version COMMAND ${CMAKE_COMMAND} - -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} - -DVERSION_HPP_IN=${VERSION_HPP_IN} - -DVERSION_HPP=${VERSION_HPP} - -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} - -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} - -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} - -DOPENMW_VERSION=${OPENMW_VERSION} + -DVERSION_HPP_IN=${VERSION_HPP_IN} + -DVERSION_HPP=${VERSION_HPP} + -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} + -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} + -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} + -DOPENMW_VERSION=${OPENMW_VERSION} -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake - VERBATIM) + VERBATIM) else (GIT_CHECKOUT) configure_file(${VERSION_HPP_IN} ${VERSION_HPP}) endif (GIT_CHECKOUT) @@ -119,7 +119,7 @@ add_component_dir (fontloader fontloader ) -add_component_dir (gameplay +add_component_dir (autocalc autocalc autocalcspell ) @@ -165,14 +165,14 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) -target_link_libraries(components +target_link_libraries(components ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_WAVE_LIBRARY} ${OGRE_LIBRARIES} - ${OENGINE_LIBRARY} + ${OENGINE_LIBRARY} ${BULLET_LIBRARIES} ) diff --git a/components/gameplay/autocalc.cpp b/components/autocalc/autocalc.cpp similarity index 98% rename from components/gameplay/autocalc.cpp rename to components/autocalc/autocalc.cpp index fbfc4a020..78cd549ca 100644 --- a/components/gameplay/autocalc.cpp +++ b/components/autocalc/autocalc.cpp @@ -37,10 +37,10 @@ namespace } } -namespace GamePlay +namespace AutoCalc { void autoCalcAttributesImpl (const ESM::NPC* npc, - const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store) + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store) { // race bonus bool male = (npc->mFlags & ESM::NPC::Female) == 0; @@ -106,7 +106,7 @@ namespace GamePlay * and by adding class, race, specialization bonus. */ void autoCalcSkillsImpl (const ESM::NPC* npc, - const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store) + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store) { for (int i = 0; i < 2; ++i) { @@ -188,7 +188,7 @@ namespace GamePlay return static_cast(floor(0.5f * (strength + endurance)) + multiplier * (level-1)); } - void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreWrap *store) + void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store) { int skills[ESM::Skill::Length]; for (int i=0; i @@ -12,7 +12,7 @@ namespace ESM struct Class; } -namespace GamePlay +namespace AutoCalc { // wrapper class for sharing the autocalc code between OpenMW and OpenCS class StatsBase @@ -35,13 +35,13 @@ namespace GamePlay }; void autoCalcAttributesImpl (const ESM::NPC* npc, - const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store); + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store); void autoCalcSkillsImpl (const ESM::NPC* npc, - const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store); + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store); unsigned short autoCalculateHealth(int level, const ESM::Class *class_, StatsBase& stats); - void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreWrap *store); + void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store); } -#endif // COMPONENTS_GAMEPLAY_AUTOCALC_H +#endif // COMPONENTS_AUTOCALC_AUTOCALC_H diff --git a/components/gameplay/autocalcspell.cpp b/components/autocalc/autocalcspell.cpp similarity index 92% rename from components/gameplay/autocalcspell.cpp rename to components/autocalc/autocalcspell.cpp index 8f2843367..8f2883e1b 100644 --- a/components/gameplay/autocalcspell.cpp +++ b/components/autocalc/autocalcspell.cpp @@ -14,7 +14,7 @@ #include "autocalc.hpp" // Most of the code in this file was moved from apps/openmw/mwmechanics/autocalcspell.cpp -namespace GamePlay +namespace AutoCalc { struct SchoolCaps @@ -27,7 +27,7 @@ namespace GamePlay }; std::vector autoCalcNpcSpells(const int *actorSkills, - const int *actorAttributes, const ESM::Race* race, StoreWrap *store) + const int *actorAttributes, const ESM::Race* race, StoreCommon *store) { static const float fNPCbaseMagickaMult = store->findGmstFloat("fNPCbaseMagickaMult"); float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; @@ -61,13 +61,13 @@ namespace GamePlay std::vector selectedSpells; - const CommonStore &spells = store->getSpells(); + const std::vector& spells = store->getSpells(); // Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the // Store must preserve the record ordering as it was in the content files. - for (CommonStore::iterator iter = spells.begin(); iter != spells.end(); ++iter) + for (std::vector::const_iterator iter = spells.begin(); iter != spells.end(); ++iter) { - const ESM::Spell* spell = &*iter; + ESM::Spell* spell = *iter; if (spell->mData.mType != ESM::Spell::ST_Spell) continue; @@ -107,7 +107,17 @@ namespace GamePlay cap.mMinCost = INT_MAX; for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) { - const ESM::Spell* testSpell = spells.find(*weakIt); + std::vector::const_iterator it = spells.begin(); + for (; it != spells.end(); ++it) + { + if ((*it)->mId == *weakIt) + break; + } + + if (it == spells.end()) + continue; + + const ESM::Spell* testSpell = *it; //int testSchool; //float dummySkillTerm; @@ -146,7 +156,7 @@ namespace GamePlay } bool attrSkillCheck (const ESM::Spell* spell, - const int* actorSkills, const int* actorAttributes, StoreWrap *store) + const int* actorSkills, const int* actorAttributes, StoreCommon *store) { const std::vector& effects = spell->mEffects.mList; for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) @@ -186,7 +196,7 @@ namespace GamePlay } void calcWeakestSchool (const ESM::Spell* spell, - const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreWrap *store) + const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreCommon *store) { float minChance = FLT_MAX; @@ -220,7 +230,7 @@ namespace GamePlay } float calcAutoCastChance(const ESM::Spell *spell, - const int *actorSkills, const int *actorAttributes, int effectiveSchool, StoreWrap *store) + const int *actorSkills, const int *actorAttributes, int effectiveSchool, StoreCommon *store) { if (spell->mData.mType != ESM::Spell::ST_Spell) return 100.f; diff --git a/components/gameplay/autocalcspell.hpp b/components/autocalc/autocalcspell.hpp similarity index 61% rename from components/gameplay/autocalcspell.hpp rename to components/autocalc/autocalcspell.hpp index ceb9f197b..769e60248 100644 --- a/components/gameplay/autocalcspell.hpp +++ b/components/autocalc/autocalcspell.hpp @@ -1,5 +1,5 @@ -#ifndef COMPONENTS_GAMEPLAY_AUTOCALCSPELL_H -#define COMPONENTS_GAMEPLAY_AUTOCALCSPELL_H +#ifndef COMPONENTS_AUTOCALC_AUTOCALCSPELL_H +#define COMPONENTS_AUTOCALC_AUTOCALCSPELL_H #include @@ -11,28 +11,29 @@ namespace ESM struct Race; } -namespace GamePlay +namespace AutoCalc { -class StoreWrap; +class StoreCommon; /// Contains algorithm for calculating an NPC's spells based on stats std::vector autoCalcNpcSpells(const int* actorSkills, - const int* actorAttributes, const ESM::Race* race, StoreWrap *store); + const int* actorAttributes, const ESM::Race* race, StoreCommon *store); // Helpers -bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, StoreWrap *store); +bool attrSkillCheck (const ESM::Spell* spell, + const int* actorSkills, const int* actorAttributes, StoreCommon *store); ESM::Skill::SkillEnum mapSchoolToSkill(int school); void calcWeakestSchool(const ESM::Spell* spell, - const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreWrap *store); + const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreCommon *store); float calcAutoCastChance(const ESM::Spell* spell, - const int* actorSkills, const int* actorAttributes, int effectiveSchool, StoreWrap *store); + const int* actorSkills, const int* actorAttributes, int effectiveSchool, StoreCommon *store); } -#endif +#endif // COMPONENTS_AUTOCALC_AUTOCALCSPELL_H diff --git a/components/autocalc/store.hpp b/components/autocalc/store.hpp new file mode 100644 index 000000000..65a29def7 --- /dev/null +++ b/components/autocalc/store.hpp @@ -0,0 +1,42 @@ +#ifndef COMPONENTS_AUTOCALC_STORE_H +#define COMPONENTS_AUTOCALC_STORE_H + +#include +#include + +namespace Loading +{ + class Listener; +} + +namespace ESM +{ + class ESMWriter; + class ESMReader; + struct Spell; + struct Skill; + struct MagicEffect; +} + +namespace AutoCalc +{ + // interface class for sharing the autocalc component between OpenMW and OpenCS + class StoreCommon + { + + public: + StoreCommon() {} + virtual ~StoreCommon() {} + + virtual int findGmstInt(const std::string& gmst) const = 0; + + virtual float findGmstFloat(const std::string& gmst) const = 0; + + virtual const ESM::Skill *findSkill(int index) const = 0; + + virtual const ESM::MagicEffect* findMagicEffect(int id) const = 0; + + virtual const std::vector& getSpells() const = 0; + }; +} +#endif // COMPONENTS_AUTOCALC_STORE_H diff --git a/components/gameplay/store.hpp b/components/gameplay/store.hpp deleted file mode 100644 index c4d7cba17..000000000 --- a/components/gameplay/store.hpp +++ /dev/null @@ -1,138 +0,0 @@ -#ifndef COMPONENTS_GAMEPLAY_STORE_H -#define COMPONENTS_GAMEPLAY_STORE_H - -#include -#include - -namespace Loading -{ - class Listener; -} - -namespace ESM -{ - class ESMWriter; - class ESMReader; - struct Spell; - struct Skill; - struct MagicEffect; -} - -namespace GamePlay -{ - // moved from apps/openmw/mwworld/store.hpp - struct StoreBase - { - virtual ~StoreBase() {} - - virtual void setUp() {} - virtual void listIdentifier(std::vector &list) const {} - - virtual size_t getSize() const = 0; - virtual int getDynamicSize() const { return 0; } - virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; - - virtual bool eraseStatic(const std::string &id) {return false;} - virtual void clearDynamic() {} - - virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - - virtual void read (ESM::ESMReader& reader, const std::string& id) {} - ///< Read into dynamic storage - }; - - // moved from apps/openmw/mwworld/store.hpp - template - class SharedIterator - { - typedef typename std::vector::const_iterator Iter; - - Iter mIter; - - public: - SharedIterator() {} - - SharedIterator(const SharedIterator &orig) - : mIter(orig.mIter) - {} - - SharedIterator(const Iter &iter) - : mIter(iter) - {} - - SharedIterator &operator++() { - ++mIter; - return *this; - } - - SharedIterator operator++(int) { - SharedIterator iter = *this; - ++mIter; - - return iter; - } - - SharedIterator &operator--() { - --mIter; - return *this; - } - - SharedIterator operator--(int) { - SharedIterator iter = *this; - --mIter; - - return iter; - } - - bool operator==(const SharedIterator &x) const { - return mIter == x.mIter; - } - - bool operator!=(const SharedIterator &x) const { - return !(*this == x); - } - - const T &operator*() const { - return **mIter; - } - - const T *operator->() const { - return &(**mIter); - } - }; - - // interface class for sharing the autocalc component between OpenMW and OpenCS - template - class CommonStore : public StoreBase - { - - public: - typedef SharedIterator iterator; - - virtual iterator begin() const = 0; - - virtual iterator end() const = 0; - - virtual const T *find(const std::string &id) const = 0; - }; - - // interface class for sharing the autocalc component between OpenMW and OpenCS - class StoreWrap - { - - public: - StoreWrap() {} - virtual ~StoreWrap() {} - - virtual int findGmstInt(const std::string& gmst) const = 0; - - virtual float findGmstFloat(const std::string& gmst) const = 0; - - virtual const ESM::Skill *findSkill(int index) const = 0; - - virtual const ESM::MagicEffect* findMagicEffect(int id) const = 0; - - virtual const CommonStore& getSpells() const = 0; - }; -} -#endif // COMPONENTS_GAMEPLAY_STORE_H From b1f07ba4fb39529faa433123a2e62a06936511bf Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 25 Jun 2015 18:57:32 +1000 Subject: [PATCH 045/441] Resolve merge issues and change the getSpells() interface. --- apps/opencs/model/world/data.cpp | 17 ++++++----------- apps/opencs/view/world/nestedtable.cpp | 16 ++++++++-------- apps/openmw/mwworld/mwstore.cpp | 5 +++-- apps/openmw/mwworld/mwstore.hpp | 2 +- apps/openmw/mwworld/store.hpp | 4 ---- components/autocalc/autocalcspell.cpp | 4 ++-- components/autocalc/store.hpp | 2 +- 7 files changed, 21 insertions(+), 29 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index b789bd28f..47115f378 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -33,7 +33,6 @@ namespace const CSMWorld::IdCollection& mSkillTable; const CSMWorld::IdCollection& mMagicEffectTable; const CSMWorld::NestedIdCollection& mSpells; - std::vector mLocal; public: @@ -42,14 +41,8 @@ namespace const CSMWorld::IdCollection& magicEffects, const CSMWorld::NestedIdCollection& spells) : mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpells(spells) - { - // prepare data in a format used by OpenMW store - for (int index = 0; index < mSpells.getSize(); ++index) - { - ESM::Spell *spell = const_cast(&mSpells.getRecord(index).get()); - mLocal.push_back(spell); - } - } + { } + ~CSStore() {} virtual int findGmstInt(const std::string& name) const @@ -74,9 +67,11 @@ namespace return &mMagicEffectTable.getRecord(ESM::MagicEffect::indexToId((short)id)).get(); } - virtual const std::vector& getSpells() const + virtual void getSpells(std::vector& spells) { - return mLocal; + // prepare data in a format used by OpenMW store + for (int index = 0; index < mSpells.getSize(); ++index) + spells.push_back(const_cast(&mSpells.getRecord(index).get())); } }; diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 5d37947d2..e4447397d 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -16,6 +16,8 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, QWidget* parent, bool editable) : DragRecordTable(document, parent), + mAddNewRowAction(0), + mRemoveRowAction(0), mModel(model) { setSelectionBehavior (QAbstractItemView::SelectRows); @@ -50,8 +52,6 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, setItemDelegateForColumn(i, delegate); } - setModel(model); - mAddNewRowAction = new QAction (tr ("Add new row"), this); connect(mAddNewRowAction, SIGNAL(triggered()), @@ -90,15 +90,15 @@ void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) void CSVWorld::NestedTable::removeRowActionTriggered() { mDocument.getUndoStack().push(new CSMWorld::DeleteNestedCommand(*(mModel->model()), - mModel->getParentId(), - selectionModel()->selectedRows().begin()->row(), - mModel->getParentColumn())); + mModel->getParentId(), + selectionModel()->selectedRows().begin()->row(), + mModel->getParentColumn())); } void CSVWorld::NestedTable::addNewRowActionTriggered() { mDocument.getUndoStack().push(new CSMWorld::AddNestedCommand(*(mModel->model()), - mModel->getParentId(), - selectionModel()->selectedRows().size(), - mModel->getParentColumn())); + mModel->getParentId(), + selectionModel()->selectedRows().size(), + mModel->getParentColumn())); } diff --git a/apps/openmw/mwworld/mwstore.cpp b/apps/openmw/mwworld/mwstore.cpp index 8ebe91cd9..bdc61033e 100644 --- a/apps/openmw/mwworld/mwstore.cpp +++ b/apps/openmw/mwworld/mwstore.cpp @@ -29,8 +29,9 @@ namespace MWWorld return MWBase::Environment::get().getWorld()->getStore().get().find(id); } - const std::vector& MWStore::getSpells() const + void MWStore::getSpells(std::vector& spells) { - return MWBase::Environment::get().getWorld()->getStore().get().getShared(); + for (Store::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) + spells.push_back(const_cast(&*iter)); } } diff --git a/apps/openmw/mwworld/mwstore.hpp b/apps/openmw/mwworld/mwstore.hpp index c43c58931..ba5060b0f 100644 --- a/apps/openmw/mwworld/mwstore.hpp +++ b/apps/openmw/mwworld/mwstore.hpp @@ -27,7 +27,7 @@ namespace MWWorld virtual const ESM::MagicEffect* findMagicEffect(int id) const; - virtual const std::vector& getSpells() const; + virtual void MWStore::getSpells(std::vector& spells); }; } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index c86a92f7a..ab09782b1 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -234,10 +234,6 @@ namespace MWWorld return mShared.size(); } - const std::vector& getShared() const { - return mShared; - } - int getDynamicSize() const { return static_cast (mDynamic.size()); // truncated from unsigned __int64 if _MSC_VER && _WIN64 diff --git a/components/autocalc/autocalcspell.cpp b/components/autocalc/autocalcspell.cpp index 8f2883e1b..01c25a695 100644 --- a/components/autocalc/autocalcspell.cpp +++ b/components/autocalc/autocalcspell.cpp @@ -60,8 +60,8 @@ namespace AutoCalc } std::vector selectedSpells; - - const std::vector& spells = store->getSpells(); + std::vector spells; + store->getSpells(spells); // Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the // Store must preserve the record ordering as it was in the content files. diff --git a/components/autocalc/store.hpp b/components/autocalc/store.hpp index 65a29def7..67061eef9 100644 --- a/components/autocalc/store.hpp +++ b/components/autocalc/store.hpp @@ -36,7 +36,7 @@ namespace AutoCalc virtual const ESM::MagicEffect* findMagicEffect(int id) const = 0; - virtual const std::vector& getSpells() const = 0; + virtual void getSpells(std::vector& spells) = 0; }; } #endif // COMPONENTS_AUTOCALC_STORE_H From 58923591cba8130feb277ad04256ac985aa67683 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 25 Jun 2015 19:25:47 +1000 Subject: [PATCH 046/441] Remove extra qualification. --- apps/openmw/mwworld/mwstore.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/mwstore.hpp b/apps/openmw/mwworld/mwstore.hpp index ba5060b0f..b833f1503 100644 --- a/apps/openmw/mwworld/mwstore.hpp +++ b/apps/openmw/mwworld/mwstore.hpp @@ -27,7 +27,7 @@ namespace MWWorld virtual const ESM::MagicEffect* findMagicEffect(int id) const; - virtual void MWStore::getSpells(std::vector& spells); + virtual void getSpells(std::vector& spells); }; } From 705253e4560b3c5a8e7c46c50363ffeb69c3b9d5 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 25 Jun 2015 19:54:07 +1000 Subject: [PATCH 047/441] Fix initialisation order. --- apps/opencs/model/world/data.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 47115f378..fc674b812 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -131,8 +131,8 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), - mReferenceables(self()) + mResourcesManager (resourcesManager), mReader (0), mDialogue (0), + mReferenceables(self()), mReaderIndex(0) { int index = 0; From 67b6c86a59909641b6f4905d1fc0a3045ce06907 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 25 Jun 2015 20:34:27 +1000 Subject: [PATCH 048/441] Fix initialisation order - properly this time. --- apps/opencs/model/world/data.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index fc674b812..ecb230f58 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -130,9 +130,8 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec } CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) -: mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mResourcesManager (resourcesManager), mReader (0), mDialogue (0), - mReferenceables(self()), mReaderIndex(0) +: mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), mReferenceables(self()), + mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0) { int index = 0; From 273ff1cccbfa779795f68883916cfb22085922ac Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 26 Jun 2015 07:48:48 +1000 Subject: [PATCH 049/441] Address review comments. --- apps/opencs/model/world/data.cpp | 13 ++++++++----- apps/opencs/model/world/npcstats.cpp | 2 +- apps/opencs/model/world/npcstats.hpp | 2 +- apps/openmw/mwclass/npc.cpp | 2 +- components/autocalc/autocalc.cpp | 7 +++---- components/autocalc/autocalc.hpp | 4 ++-- components/autocalc/autocalcspell.cpp | 1 - components/autocalc/store.hpp | 7 ------- 8 files changed, 16 insertions(+), 22 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ecb230f58..856b00e90 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -130,7 +130,7 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec } CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) -: mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), mReferenceables(self()), +: mEncoder (encoding), mPathgrids (mCells), mReferenceables(self()), mRefs (mCells), mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0) { int index = 0; @@ -1526,7 +1526,7 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const if (autoCalc) level = npc.mNpdt12.mLevel; - CSMWorld::NpcStats *stats = new CSMWorld::NpcStats(); + std::auto_ptr stats (new CSMWorld::NpcStats()); CSStore store(mGmsts, mSkills, mMagicEffects, mSpells); @@ -1547,7 +1547,7 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const for (std::vector::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it) { - stats->addSpells(*it); + stats->addSpell(*it); } } @@ -1605,8 +1605,11 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const } } - emit cacheNpcStats (npc.mId, stats); - return stats; + if (stats.get() == 0) + return 0; + + emit cacheNpcStats (npc.mId, stats.release()); + return stats.release(); } void CSMWorld::Data::cacheNpcStatsEvent (const std::string& id, CSMWorld::NpcStats *stats) diff --git a/apps/opencs/model/world/npcstats.cpp b/apps/opencs/model/world/npcstats.cpp index da14b473e..5b8484934 100644 --- a/apps/opencs/model/world/npcstats.cpp +++ b/apps/opencs/model/world/npcstats.cpp @@ -45,7 +45,7 @@ namespace CSMWorld mAttr[index] = value; } - void NpcStats::addSpells(std::string id) + void NpcStats::addSpell(const std::string& id) { struct SpellInfo info; info.mName = id; diff --git a/apps/opencs/model/world/npcstats.hpp b/apps/opencs/model/world/npcstats.hpp index 4d9be149f..8cefe586f 100644 --- a/apps/opencs/model/world/npcstats.hpp +++ b/apps/opencs/model/world/npcstats.hpp @@ -43,7 +43,7 @@ namespace CSMWorld virtual void setAttribute(int index, unsigned char value); - virtual void addSpells(std::string id); + virtual void addSpell(const std::string& id); void addPowers(const std::string& id, int type); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 64ed868a1..3b0234a25 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -74,7 +74,7 @@ namespace virtual void setAttribute(int index, unsigned char value) { mNpcStats.setAttribute(index, value); } - virtual void addSpells(std::string id) { mNpcStats.getSpells().add(id); } + virtual void addSpell(const std::string& id) { mNpcStats.getSpells().add(id); } virtual unsigned char getBaseSkill(int index) const { return mNpcStats.getSkill(index).getBase(); } diff --git a/components/autocalc/autocalc.cpp b/components/autocalc/autocalc.cpp index 78cd549ca..122d19763 100644 --- a/components/autocalc/autocalc.cpp +++ b/components/autocalc/autocalc.cpp @@ -11,7 +11,6 @@ #include "autocalcspell.hpp" -// Most of the code in this file was moved from apps/openmw/mwclass/npc.cpp namespace { int is_even(double d) @@ -117,7 +116,7 @@ namespace AutoCalc int index = class_->mData.mSkills[i2][i]; if (index >= 0 && index < ESM::Skill::Length) { - stats.setBaseSkill (index, stats.getBaseSkill(index) + bonus); + stats.setBaseSkill (index, bonus); } } } @@ -168,7 +167,7 @@ namespace AutoCalc } } - unsigned short autoCalculateHealth(int level, const ESM::Class *class_, StatsBase& stats) + unsigned short autoCalculateHealth(int level, const ESM::Class *class_, const StatsBase& stats) { // initial health int strength = stats.getBaseAttribute(ESM::Attribute::Strength); @@ -200,7 +199,7 @@ namespace AutoCalc std::vector spells = autoCalcNpcSpells(skills, attributes, race, store); for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) - stats.addSpells(*it); + stats.addSpell(*it); } StatsBase::StatsBase() {} diff --git a/components/autocalc/autocalc.hpp b/components/autocalc/autocalc.hpp index 3f4b6bae1..5cfe06b42 100644 --- a/components/autocalc/autocalc.hpp +++ b/components/autocalc/autocalc.hpp @@ -27,7 +27,7 @@ namespace AutoCalc virtual void setAttribute(int index, unsigned char value) = 0; - virtual void addSpells(std::string id) = 0; + virtual void addSpell(const std::string& id) = 0; virtual unsigned char getBaseSkill(int index) const = 0; @@ -40,7 +40,7 @@ namespace AutoCalc void autoCalcSkillsImpl (const ESM::NPC* npc, const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store); - unsigned short autoCalculateHealth(int level, const ESM::Class *class_, StatsBase& stats); + unsigned short autoCalculateHealth(int level, const ESM::Class *class_, const StatsBase& stats); void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store); } diff --git a/components/autocalc/autocalcspell.cpp b/components/autocalc/autocalcspell.cpp index 01c25a695..78499a092 100644 --- a/components/autocalc/autocalcspell.cpp +++ b/components/autocalc/autocalcspell.cpp @@ -13,7 +13,6 @@ #include "autocalc.hpp" -// Most of the code in this file was moved from apps/openmw/mwmechanics/autocalcspell.cpp namespace AutoCalc { diff --git a/components/autocalc/store.hpp b/components/autocalc/store.hpp index 67061eef9..9a798a5ce 100644 --- a/components/autocalc/store.hpp +++ b/components/autocalc/store.hpp @@ -4,15 +4,8 @@ #include #include -namespace Loading -{ - class Listener; -} - namespace ESM { - class ESMWriter; - class ESMReader; struct Spell; struct Skill; struct MagicEffect; From 8c39f2b3769eeea40e36fcf52dceb514e4a27ac9 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 26 Jun 2015 08:52:39 +1000 Subject: [PATCH 050/441] Fix dereferencing a null pointer. --- apps/opencs/model/world/data.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 856b00e90..3cd501b4f 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1608,8 +1608,9 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const if (stats.get() == 0) return 0; - emit cacheNpcStats (npc.mId, stats.release()); - return stats.release(); + CSMWorld::NpcStats *result = stats.release(); + emit cacheNpcStats (npc.mId, result); + return result; } void CSMWorld::Data::cacheNpcStatsEvent (const std::string& id, CSMWorld::NpcStats *stats) From 78457a82345ebd04dada271c0e6004e0d40f9fbe Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 26 Jun 2015 13:50:09 +1000 Subject: [PATCH 051/441] Simplify npc data update and fix data copy when autocal flag changed. --- apps/opencs/model/world/data.cpp | 75 ++++++++------------------------ 1 file changed, 17 insertions(+), 58 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 3cd501b4f..3cb5d3cbb 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1373,70 +1373,33 @@ void CSMWorld::Data::npcDataChanged (const QModelIndex& topLeft, const QModelInd static_cast(getTableModel(CSMWorld::UniversalId::Type_Referenceable)); int autoCalcColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_AutoCalc); - int miscColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_NpcMisc); - // first check for level - bool levelChanged = false; - if (topLeft.parent().isValid() && bottomRight.parent().isValid()) - { - if (topLeft.parent().column() <= miscColumn && miscColumn <= bottomRight.parent().column()) - { - for (int col = topLeft.column(); col <= bottomRight.column(); ++col) - { - int role = objectModel->nestedHeaderData(topLeft.parent().column(), - col, Qt::Horizontal, CSMWorld::ColumnBase::Role_ColumnId).toInt(); - if (role == CSMWorld::Columns::ColumnId_NpcLevel) - { - levelChanged = true; - break; - } - } - } - } - - // next check for autocalc - bool autoCalcChanged = false; - if (!topLeft.parent().isValid() && !bottomRight.parent().isValid()) + // check for autocalc + if (topLeft.parent().isValid() || bottomRight.parent().isValid() + || topLeft.column() > autoCalcColumn || autoCalcColumn > bottomRight.column()) { - if ((topLeft.column() <= autoCalcColumn && autoCalcColumn <= bottomRight.column()) - || (topLeft.column() <= miscColumn && miscColumn <= bottomRight.column())) - { - autoCalcChanged = true; - } + return; } - if (!levelChanged && !autoCalcChanged) - return; - - int row = 0; - int end = 0; - if (topLeft.parent().isValid()) - row = topLeft.parent().row(); - else - row = topLeft.row(); - - if (bottomRight.parent().isValid()) - end = bottomRight.parent().row(); - else - end = bottomRight.row(); - - for (; row <= end; ++row) + int row = topLeft.row(); + for (; row <= bottomRight.row(); ++row) { Record record = static_cast&>(mReferenceables.getRecord(row)); ESM::NPC &npc = record.get(); - // If going from autocalc to non-autocalc, save the autocalc values - if (autoCalcChanged) + if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) - saveAutoCalcValues(npc); // update attributes and skills - else - npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc - - record.setModified(npc); - mReferenceables.replace(row, record); + // first pretend autocalc to force recalculation + npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; + saveAutoCalcValues(npc); // update attributes and skills + npc.mNpdtType = ESM::NPC::NPC_DEFAULT; } + else + npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc + + record.setModified(npc); + mReferenceables.replace(row, record); } } @@ -1469,11 +1432,7 @@ void CSMWorld::Data::gmstDataChanged (const QModelIndex& topLeft, const QModelIn // FIXME: how to undo? void CSMWorld::Data::saveAutoCalcValues(ESM::NPC& npc) { - CSMWorld::NpcStats * cachedStats = getCachedNpcData (npc.mId); - if (!cachedStats) - return; // silently fail - - CSMWorld::NpcStats* stats = npcAutoCalculate(npc); + CSMWorld::NpcStats *stats = npcAutoCalculate(npc); // update npc npc.mNpdt52.mLevel = npc.mNpdt12.mLevel; From 3e29bb8a860cfdc3dc58855b616f2fc2f38b0a05 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 26 Jun 2015 16:10:50 +1000 Subject: [PATCH 052/441] Fix undo for NPC autocalc changes. Fix the lack of refresh after race powers subtable. --- apps/opencs/model/world/data.cpp | 83 +++------------------ apps/opencs/model/world/data.hpp | 2 - apps/opencs/model/world/refidadapterimp.cpp | 47 +++++++++++- apps/opencs/model/world/refidadapterimp.hpp | 3 +- apps/opencs/model/world/refidcollection.cpp | 2 +- 5 files changed, 58 insertions(+), 79 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 3cb5d3cbb..6d17e90b7 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1324,25 +1324,33 @@ void CSMWorld::Data::raceDataChanged (const QModelIndex& topLeft, const QModelIn // affects racial bonus attributes & skills // - mData.mAttributeValues[] // - mData.mBonus[].mBonus + // - mPowers.mList[] CSMWorld::IdTree *raceModel = static_cast(getTableModel(CSMWorld::UniversalId::Type_Race)); int attrColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceAttributes); int bonusColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceSkillBonus); + int powersColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_PowerList); bool match = false; + int raceRow = topLeft.row(); + int raceEnd = bottomRight.row(); if (topLeft.parent().isValid() && bottomRight.parent().isValid()) { if ((topLeft.parent().column() <= attrColumn && attrColumn <= bottomRight.parent().column()) - || (topLeft.parent().column() <= bonusColumn && bonusColumn <= bottomRight.parent().column())) + || (topLeft.parent().column() <= bonusColumn && bonusColumn <= bottomRight.parent().column()) + || (topLeft.parent().column() <= powersColumn && powersColumn <= bottomRight.parent().column())) { match = true; // TODO: check for specific nested column? + raceRow = topLeft.parent().row(); + raceEnd = bottomRight.parent().row(); } } else { if ((topLeft.column() <= attrColumn && attrColumn <= bottomRight.column()) - || (topLeft.column() <= bonusColumn && bonusColumn <= bottomRight.column())) + || (topLeft.column() <= bonusColumn && bonusColumn <= bottomRight.column()) + || (topLeft.column() <= powersColumn && powersColumn <= bottomRight.column())) { match = true; // maybe the whole table changed } @@ -1353,7 +1361,7 @@ void CSMWorld::Data::raceDataChanged (const QModelIndex& topLeft, const QModelIn // update autocalculated attributes/skills of every NPC with matching race int idColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); - for (int raceRow = topLeft.parent().row(); raceRow <= bottomRight.parent().row(); ++raceRow) + for (; raceRow <= raceEnd; ++raceRow) { clearNpcStatsCache(); @@ -1363,44 +1371,10 @@ void CSMWorld::Data::raceDataChanged (const QModelIndex& topLeft, const QModelIn } } -// FIXME: currently ignoring level changes void CSMWorld::Data::npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { + // TODO: for now always recalculate clearNpcStatsCache(); - - // Either autoCalc flag changed or NPC level changed - CSMWorld::IdTree *objectModel = - static_cast(getTableModel(CSMWorld::UniversalId::Type_Referenceable)); - - int autoCalcColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_AutoCalc); - - // check for autocalc - if (topLeft.parent().isValid() || bottomRight.parent().isValid() - || topLeft.column() > autoCalcColumn || autoCalcColumn > bottomRight.column()) - { - return; - } - - int row = topLeft.row(); - for (; row <= bottomRight.row(); ++row) - { - Record record = - static_cast&>(mReferenceables.getRecord(row)); - ESM::NPC &npc = record.get(); - - if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) - { - // first pretend autocalc to force recalculation - npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; - saveAutoCalcValues(npc); // update attributes and skills - npc.mNpdtType = ESM::NPC::NPC_DEFAULT; - } - else - npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc - - record.setModified(npc); - mReferenceables.replace(row, record); - } } void CSMWorld::Data::gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) @@ -1429,39 +1403,6 @@ void CSMWorld::Data::gmstDataChanged (const QModelIndex& topLeft, const QModelIn emit updateNpcAutocalc(0/*all*/, empty); } -// FIXME: how to undo? -void CSMWorld::Data::saveAutoCalcValues(ESM::NPC& npc) -{ - CSMWorld::NpcStats *stats = npcAutoCalculate(npc); - - // update npc - npc.mNpdt52.mLevel = npc.mNpdt12.mLevel; - - npc.mNpdt52.mStrength = stats->getBaseAttribute(ESM::Attribute::Strength); - npc.mNpdt52.mIntelligence = stats->getBaseAttribute(ESM::Attribute::Intelligence); - npc.mNpdt52.mWillpower = stats->getBaseAttribute(ESM::Attribute::Willpower); - npc.mNpdt52.mAgility = stats->getBaseAttribute(ESM::Attribute::Agility); - npc.mNpdt52.mSpeed = stats->getBaseAttribute(ESM::Attribute::Speed); - npc.mNpdt52.mEndurance = stats->getBaseAttribute(ESM::Attribute::Endurance); - npc.mNpdt52.mPersonality = stats->getBaseAttribute(ESM::Attribute::Personality); - npc.mNpdt52.mLuck = stats->getBaseAttribute(ESM::Attribute::Luck); - - for (int i = 0; i < ESM::Skill::Length; ++i) - { - npc.mNpdt52.mSkills[i] = stats->getBaseSkill(i); - } - - npc.mNpdt52.mHealth = stats->getHealth(); - npc.mNpdt52.mMana = stats->getMana(); - npc.mNpdt52.mFatigue = stats->getFatigue(); - npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition; - npc.mNpdt52.mReputation = npc.mNpdt12.mReputation; - npc.mNpdt52.mRank = npc.mNpdt12.mRank; - npc.mNpdt52.mGold = npc.mNpdt12.mGold; - - // TODO: add spells from autogenerated list like vanilla (but excluding any race powers or abilities) -} - void CSMWorld::Data::clearNpcStatsCache () { for (std::map::iterator it (mNpcStatCache.begin()); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index fdc76fd73..e081318a6 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -126,8 +126,6 @@ namespace CSMWorld const Data& self (); - void saveAutoCalcValues(ESM::NPC& npc); - void clearNpcStatsCache (); public: diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 99ca76d73..2bb980bfe 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -558,8 +558,8 @@ CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) mMisc(NULL) {} -CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) -: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns) +CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns, const CSMWorld::Data& data) +: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns), mData(data) {} QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) @@ -634,8 +634,47 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d npc.mFlags &= ~iter->second; if (iter->second == ESM::NPC::Autocalc) - npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS - : ESM::NPC::NPC_DEFAULT; + { + if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + { + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + + // update npc + npc.mNpdtType = ESM::NPC::NPC_DEFAULT; + npc.mNpdt52.mLevel = npc.mNpdt12.mLevel; + + npc.mNpdt52.mStrength = stats->getBaseAttribute(ESM::Attribute::Strength); + npc.mNpdt52.mIntelligence = stats->getBaseAttribute(ESM::Attribute::Intelligence); + npc.mNpdt52.mWillpower = stats->getBaseAttribute(ESM::Attribute::Willpower); + npc.mNpdt52.mAgility = stats->getBaseAttribute(ESM::Attribute::Agility); + npc.mNpdt52.mSpeed = stats->getBaseAttribute(ESM::Attribute::Speed); + npc.mNpdt52.mEndurance = stats->getBaseAttribute(ESM::Attribute::Endurance); + npc.mNpdt52.mPersonality = stats->getBaseAttribute(ESM::Attribute::Personality); + npc.mNpdt52.mLuck = stats->getBaseAttribute(ESM::Attribute::Luck); + + for (int i = 0; i < ESM::Skill::Length; ++i) + { + npc.mNpdt52.mSkills[i] = stats->getBaseSkill(i); + } + + npc.mNpdt52.mHealth = stats->getHealth(); + npc.mNpdt52.mMana = stats->getMana(); + npc.mNpdt52.mFatigue = stats->getFatigue(); + npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition; + npc.mNpdt52.mReputation = npc.mNpdt12.mReputation; + npc.mNpdt52.mRank = npc.mNpdt12.mRank; + npc.mNpdt52.mGold = npc.mNpdt12.mGold; + + // TODO: add spells from autogenerated list like vanilla (but excluding any + // race powers or abilities) + } + else + { + npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; + npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc + mData.npcAutoCalculate(npc); + } + } } else { diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index acdc124eb..4a8288fa4 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -807,10 +807,11 @@ namespace CSMWorld class NpcRefIdAdapter : public ActorRefIdAdapter { NpcColumns mColumns; + const Data& mData; public: - NpcRefIdAdapter (const NpcColumns& columns); + NpcRefIdAdapter (const NpcColumns& columns, const Data& data); virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const; diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index d5232db63..fc52fe388 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -620,7 +620,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous, new MiscRefIdAdapter (inventoryColumns, key))); mAdapters.insert (std::make_pair (UniversalId::Type_Npc, - new NpcRefIdAdapter (npcColumns))); + new NpcRefIdAdapter (npcColumns, data))); mAdapters.insert (std::make_pair (UniversalId::Type_Probe, new ToolRefIdAdapter (UniversalId::Type_Probe, toolsColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Repair, From 6542ff111dd311c732f3c53b551c8b9d3022be06 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 27 Jun 2015 08:50:53 +1000 Subject: [PATCH 053/441] Fix merge issues. --- apps/opencs/view/world/dialoguesubview.cpp | 31 +++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 6ab5b18ac..a366f53ab 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -66,7 +66,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo CSMWorld::Columns::ColumnId columnId = static_cast ( mTable->getColumnId (index.column())); - + if (QVariant::String == v.type()) { label->setText(v.toString()); @@ -75,7 +75,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo { int data = v.toInt(); std::vector enumNames (CSMWorld::Columns::getEnums (columnId)); - + label->setText(QString::fromUtf8(enumNames.at(data).c_str())); } else @@ -709,6 +709,30 @@ void CSVWorld::SimpleDialogueSubView::changeCurrentId (const std::string& newId) mCommandDispatcher.setSelection(selection); } +void CSVWorld::SimpleDialogueSubView::refreshNpcDialogue (int type, const std::string& id) +{ + int typeColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + if (CSMWorld::UniversalId::Type_Npc + != mTable->data(mTable->getModelIndex(mCurrentId, typeColumn), Qt::DisplayRole).toInt()) + { + return; + } + + int raceColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Race); + int classColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Class); + + if ((type == 0/*FIXME*/ && id == "") // skill or gmst changed + || (id == mTable->data(mTable->getModelIndex(mCurrentId, raceColumn), + Qt::DisplayRole).toString().toUtf8().constData()) // race + || (id == mTable->data(mTable->getModelIndex(mCurrentId, classColumn), + Qt::DisplayRole).toString().toUtf8().constData())) // class + { + int y = mEditWidget->verticalScrollBar()->value(); + mEditWidget->remake (mTable->getModelIndex(mCurrentId, 0).row()); + mEditWidget->verticalScrollBar()->setValue(y); + } +} + CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) : SimpleDialogueSubView (id, document) @@ -783,7 +807,7 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, deleteButton->setDisabled (true); } - getMainLayout().addLayout (buttonsLayout); + getMainLayout().addLayout (buttonsLayout); } void CSVWorld::DialogueSubView::cloneRequest() @@ -864,7 +888,6 @@ void CSVWorld::DialogueSubView::nextId () } } - void CSVWorld::DialogueSubView::showPreview () { QModelIndex currentIndex (getTable().getModelIndex (getCurrentId(), 0)); From 5602992b86032c2203af472c8b729af76bd5b13e Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 26 Jun 2015 22:14:11 +0300 Subject: [PATCH 054/441] Add the ability to search a nested column index Conflicts: apps/opencs/model/world/idtree.cpp apps/opencs/model/world/idtree.hpp --- apps/opencs/model/world/idtree.cpp | 10 ++++++++ apps/opencs/model/world/idtree.hpp | 6 +++++ apps/opencs/model/world/nestedcollection.cpp | 25 ++++++++++++++++++++ apps/opencs/model/world/nestedcollection.hpp | 8 +++++++ 4 files changed, 49 insertions(+) diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index fba672108..545ba383d 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -266,3 +266,13 @@ void CSMWorld::IdTree::updateNpcAutocalc (int type, const std::string& id) { emit refreshNpcDialogue (type, id); } + +int CSMWorld::IdTree::searchNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + return mNestedCollection->searchNestedColumnIndex(parentColumn, id); +} + +int CSMWorld::IdTree::findNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + return mNestedCollection->findNestedColumnIndex(parentColumn, id); +} diff --git a/apps/opencs/model/world/idtree.hpp b/apps/opencs/model/world/idtree.hpp index b29a3ae93..8e464dd95 100644 --- a/apps/opencs/model/world/idtree.hpp +++ b/apps/opencs/model/world/idtree.hpp @@ -73,6 +73,12 @@ namespace CSMWorld virtual bool hasChildren (const QModelIndex& index) const; + virtual int searchNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or -1 if the requested column wasn't found. + + virtual int findNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or throws an exception if the requested column wasn't found. + signals: void resetStart(const QString& id); diff --git a/apps/opencs/model/world/nestedcollection.cpp b/apps/opencs/model/world/nestedcollection.cpp index 937ad6ad6..850d8c385 100644 --- a/apps/opencs/model/world/nestedcollection.cpp +++ b/apps/opencs/model/world/nestedcollection.cpp @@ -15,3 +15,28 @@ int CSMWorld::NestedCollection::getNestedColumnsCount(int row, int column) const { return 0; } + +int CSMWorld::NestedCollection::searchNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + // Assumed that the parentColumn is always a valid index + const NestableColumn *parent = getNestableColumn(parentColumn); + int nestedColumnCount = getNestedColumnsCount(0, parentColumn); + for (int i = 0; i < nestedColumnCount; ++i) + { + if (parent->nestedColumn(i).mColumnId == id) + { + return i; + } + } + return -1; +} + +int CSMWorld::NestedCollection::findNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + int index = searchNestedColumnIndex(parentColumn, id); + if (index == -1) + { + throw std::logic_error("CSMWorld::NestedCollection: No such nested column"); + } + return index; +} diff --git a/apps/opencs/model/world/nestedcollection.hpp b/apps/opencs/model/world/nestedcollection.hpp index b075f53c4..4548cfb2b 100644 --- a/apps/opencs/model/world/nestedcollection.hpp +++ b/apps/opencs/model/world/nestedcollection.hpp @@ -1,6 +1,8 @@ #ifndef CSM_WOLRD_NESTEDCOLLECTION_H #define CSM_WOLRD_NESTEDCOLLECTION_H +#include "columns.hpp" + class QVariant; namespace CSMWorld @@ -33,6 +35,12 @@ namespace CSMWorld virtual int getNestedColumnsCount(int row, int column) const; virtual NestableColumn *getNestableColumn(int column) = 0; + + virtual int searchNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or -1 if the requested column wasn't found. + + virtual int findNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or throws an exception if the requested column wasn't found. }; } From 0420f21462efa0bfe7f3c82b5a4f6ae9bc43e726 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 26 Jun 2015 22:16:12 +0300 Subject: [PATCH 055/441] Add the ability to add nested values to the Create command --- apps/opencs/model/world/commands.cpp | 24 ++++++++++++++++++++++++ apps/opencs/model/world/commands.hpp | 5 +++++ 2 files changed, 29 insertions(+) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index a44d8770f..5e0cc8f88 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -58,6 +58,25 @@ void CSMWorld::CreateCommand::applyModifications() { for (std::map::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) mModel.setData (mModel.getModelIndex (mId, iter->first), iter->second); + + if (!mNestedValues.empty()) + { + CSMWorld::IdTree *tree = dynamic_cast(&mModel); + if (tree == NULL) + { + throw std::logic_error("CSMWorld::CreateCommand: Attempt to add nested values to the non-nested model"); + } + + std::map >::const_iterator current = mNestedValues.begin(); + std::map >::const_iterator end = mNestedValues.end(); + for (; current != end; ++current) + { + QModelIndex index = tree->index(0, + current->second.first, + tree->getNestedModelIndex(mId, current->first)); + tree->setData(index, current->second.second); + } + } } CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand* parent) @@ -71,6 +90,11 @@ void CSMWorld::CreateCommand::addValue (int column, const QVariant& value) mValues[column] = value; } +void CSMWorld::CreateCommand::addNestedValue(int parentColumn, int nestedColumn, const QVariant &value) +{ + mNestedValues[parentColumn] = std::make_pair(nestedColumn, value); +} + void CSMWorld::CreateCommand::setType (UniversalId::Type type) { mType = type; diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index cdd398153..81c40d0ab 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -48,6 +48,9 @@ namespace CSMWorld class CreateCommand : public QUndoCommand { std::map mValues; + std::map > mNestedValues; + ///< Parameter order: a parent column, a nested column, a data. + ///< A nested row has index of 0. protected: @@ -68,6 +71,8 @@ namespace CSMWorld void addValue (int column, const QVariant& value); + void addNestedValue(int parentColumn, int nestedColumn, const QVariant &value); + virtual void redo(); virtual void undo(); From 1c9d36a3686b93d2e1f7e83ddcf7c28ccffb611c Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 26 Jun 2015 23:38:20 +0300 Subject: [PATCH 056/441] Creating/cloning Cell sets the proper Interior flag --- apps/opencs/view/world/cellcreator.cpp | 12 ++++++++++++ apps/opencs/view/world/cellcreator.hpp | 3 +++ 2 files changed, 15 insertions(+) diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index cdeee5655..5dfb6af45 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -8,6 +8,9 @@ #include #include +#include "../../model/world/commands.hpp" +#include "../../model/world/idtree.hpp" + std::string CSVWorld::CellCreator::getId() const { if (mType->currentIndex()==0) @@ -20,6 +23,15 @@ std::string CSVWorld::CellCreator::getId() const return stream.str(); } +void CSVWorld::CellCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const +{ + CSMWorld::IdTree *model = dynamic_cast(getData().getTableModel(getCollectionId())); + Q_ASSERT(model != NULL); + int parentIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Cell); + int index = model->findNestedColumnIndex(parentIndex, CSMWorld::Columns::ColumnId_Interior); + command.addNestedValue(parentIndex, index, mType->currentIndex() == 0); +} + CSVWorld::CellCreator::CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) : GenericCreator (data, undoStack, id) diff --git a/apps/opencs/view/world/cellcreator.hpp b/apps/opencs/view/world/cellcreator.hpp index db9fbf8a3..4d5314f23 100644 --- a/apps/opencs/view/world/cellcreator.hpp +++ b/apps/opencs/view/world/cellcreator.hpp @@ -23,6 +23,9 @@ namespace CSVWorld virtual std::string getId() const; + /// Allow subclasses to add additional data to \a command. + virtual void configureCreateCommand(CSMWorld::CreateCommand& command) const; + public: CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); From 3a6f708405668b5f9b06c29757f37574745bfa72 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 27 Jun 2015 11:47:59 +0300 Subject: [PATCH 057/441] Cell type can be changed when cloning a cell --- apps/opencs/view/world/cellcreator.cpp | 7 ------- apps/opencs/view/world/cellcreator.hpp | 2 -- 2 files changed, 9 deletions(-) diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index 5dfb6af45..45fac2c5f 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -106,10 +106,3 @@ void CSVWorld::CellCreator::cloneMode(const std::string& originId, mType->setCurrentIndex(0); } } - - -void CSVWorld::CellCreator::toggleWidgets(bool active) -{ - CSVWorld::GenericCreator::toggleWidgets(active); - mType->setEnabled(active); -} diff --git a/apps/opencs/view/world/cellcreator.hpp b/apps/opencs/view/world/cellcreator.hpp index 4d5314f23..b633ca06e 100644 --- a/apps/opencs/view/world/cellcreator.hpp +++ b/apps/opencs/view/world/cellcreator.hpp @@ -32,8 +32,6 @@ namespace CSVWorld virtual void reset(); - virtual void toggleWidgets(bool active = true); - virtual void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type); From 9ef3dced9024e20e5113d6a65e3bf6a9e4a10b3e Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 27 Jun 2015 12:26:19 +0300 Subject: [PATCH 058/441] Close the creator when the original record is removed (in clone mode) --- apps/opencs/view/world/genericcreator.cpp | 11 +++++++++++ apps/opencs/view/world/genericcreator.hpp | 2 ++ 2 files changed, 13 insertions(+) diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index c64109608..5f04d9a7a 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -161,6 +161,8 @@ CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undo connect (mCreate, SIGNAL (clicked (bool)), this, SLOT (create())); connect (mId, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); + + connect (&mData, SIGNAL (idListChanged()), this, SLOT (dataIdListChanged())); } void CSVWorld::GenericCreator::setEditLock (bool locked) @@ -291,3 +293,12 @@ void CSVWorld::GenericCreator::scopeChanged (int index) update(); updateNamespace(); } + +void CSVWorld::GenericCreator::dataIdListChanged() +{ + // If the original ID of cloned record was removed, cancel the creator + if (mCloneMode && !mData.hasId(mClonedId)) + { + emit done(); + } +} diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 0d2a40486..471d0622e 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -113,6 +113,8 @@ namespace CSVWorld void create(); void scopeChanged (int index); + + void dataIdListChanged(); }; } From 0809a738b8cbb99bc73d9028c57361e9aba68c65 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 27 Jun 2015 21:59:16 +0300 Subject: [PATCH 059/441] Add the proper getErrors() method to CellCreator. Fix the impossibility of the Exterior Cell creation. --- apps/opencs/view/world/cellcreator.cpp | 14 ++++++++++++++ apps/opencs/view/world/cellcreator.hpp | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index 45fac2c5f..c7d909f4c 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -106,3 +106,17 @@ void CSVWorld::CellCreator::cloneMode(const std::string& originId, mType->setCurrentIndex(0); } } + +std::string CSVWorld::CellCreator::getErrors() const +{ + std::string errors; + if (mType->currentIndex() == 0) + { + errors = GenericCreator::getErrors(); + } + else if (getData().hasId(getId())) + { + errors = "The Exterior Cell is already exist"; + } + return errors; +} diff --git a/apps/opencs/view/world/cellcreator.hpp b/apps/opencs/view/world/cellcreator.hpp index b633ca06e..6c682c6cd 100644 --- a/apps/opencs/view/world/cellcreator.hpp +++ b/apps/opencs/view/world/cellcreator.hpp @@ -35,6 +35,10 @@ namespace CSVWorld virtual void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type); + virtual std::string getErrors() const; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. + private slots: void setType (int index); From d7ce441b70040c3698891d80748e2cb2d982cc8c Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 28 Jun 2015 00:31:41 +0300 Subject: [PATCH 060/441] Deleted records can be cloned --- apps/opencs/view/world/table.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index cb4acf23b..38b38bef9 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -468,7 +468,7 @@ void CSVWorld::Table::cloneRecord() { QModelIndexList selectedRows = selectionModel()->selectedRows(); const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); - if (selectedRows.size()==1 && !mModel->isDeleted (toClone.getId())) + if (selectedRows.size() == 1) { emit cloneRequest (toClone); } From bec19fde973882a276b8d98fc0bfa3565f650d3d Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Mon, 29 Jun 2015 22:45:34 +0300 Subject: [PATCH 061/441] Not accepted drag event must be ignored --- apps/opencs/view/world/dragrecordtable.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index 5e8ddae26..a5f933283 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -55,12 +55,10 @@ void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event) if (index.flags() & Qt::ItemIsEditable) { event->accept(); + return; } } - else - { - event->ignore(); - } + event->ignore(); } void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) From 8bf1b66ca231963b3c22c82ab2c1b449e70667ba Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 30 Jun 2015 23:35:54 +0300 Subject: [PATCH 062/441] Add the RecordType column to the MetaData table --- apps/opencs/model/world/data.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 27e57143f..c7e676361 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -548,6 +548,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mMetaData.addColumn (new StringIdColumn (true)); mMetaData.addColumn (new RecordStateColumn); + mMetaData.addColumn (new FixedRecordTypeColumn (UniversalId::Type_MetaData)); mMetaData.addColumn (new FormatColumn); mMetaData.addColumn (new AuthorColumn); mMetaData.addColumn (new FileDescriptionColumn); From 2cd62e19d800e3c52a7d6efdb12e1dba8ddf1081 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 3 Jul 2015 10:45:08 +0200 Subject: [PATCH 063/441] implemented sorting in report views (Fixes #2540) --- apps/opencs/view/tools/reporttable.cpp | 59 ++++++++++++++++++-------- apps/opencs/view/tools/reporttable.hpp | 17 +++++--- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index ca6b0dabf..550c53969 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "../../model/tools/reportmodel.hpp" @@ -23,7 +24,7 @@ namespace CSVTools public: RichTextDelegate (QObject *parent = 0); - + virtual void paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; }; @@ -63,7 +64,7 @@ void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - QString hint = mModel->data (mModel->index (iter->row(), 2)).toString(); + QString hint = mProxyModel->data (mProxyModel->index (iter->row(), 2)).toString(); if (!hint.isEmpty() && hint[0]=='R') { @@ -78,7 +79,7 @@ void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) if (mRefreshAction) menu.addAction (mRefreshAction); - + menu.exec (event->globalPos()); } @@ -106,14 +107,14 @@ void CSVTools::ReportTable::mouseDoubleClickEvent (QMouseEvent *event) event->accept(); return; } - + switch (iter->second) { case Action_None: event->accept(); break; - + case Action_Edit: event->accept(); @@ -152,7 +153,10 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); - setModel (mModel); + mProxyModel = new QSortFilterProxyModel (this); + mProxyModel->setSourceModel (mModel); + + setModel (mProxyModel); setColumnHidden (2, true); mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate (0, @@ -162,7 +166,7 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, if (richTextDescription) setItemDelegateForColumn (mModel->columnCount()-1, new RichTextDelegate (this)); - + mShowAction = new QAction (tr ("Show"), this); connect (mShowAction, SIGNAL (triggered()), this, SLOT (showSelection())); addAction (mShowAction); @@ -182,10 +186,10 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, connect (mRefreshAction, SIGNAL (triggered()), this, SIGNAL (refreshRequest())); addAction (mRefreshAction); } - + mDoubleClickActions.insert (std::make_pair (Qt::NoModifier, Action_Edit)); mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_Remove)); - mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_EditAndRemove)); + mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_EditAndRemove)); } std::vector CSVTools::ReportTable::getDraggedRecords() const @@ -197,7 +201,7 @@ std::vector CSVTools::ReportTable::getDraggedRecords() co for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - ids.push_back (mModel->getUniversalId (iter->row())); + ids.push_back (mModel->getUniversalId (mProxyModel->mapToSource (*iter).row())); } return ids; @@ -234,7 +238,7 @@ void CSVTools::ReportTable::updateUserSetting (const QString& name, const QStrin mDoubleClickActions[modifiers] = action; return; - } + } } std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const @@ -245,13 +249,22 @@ std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const { QModelIndexList selectedRows = selectionModel()->selectedRows(); + std::vector rows; + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - QString hint = mModel->data (mModel->index (iter->row(), 2)).toString(); + rows.push_back (mProxyModel->mapToSource (*iter).row()); + } + + std::sort (rows.begin(), rows.end()); + + for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) + { + QString hint = mModel->data (mModel->index (*iter, 2)).toString(); if (!hint.isEmpty() && hint[0]=='R') - indices.push_back (iter->row()); + indices.push_back (*iter); } } else @@ -272,25 +285,35 @@ void CSVTools::ReportTable::flagAsReplaced (int index) { mModel->flagAsReplaced (index); } - + void CSVTools::ReportTable::showSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) - emit editRequest (mModel->getUniversalId (iter->row()), mModel->getHint (iter->row())); + { + int row = mProxyModel->mapToSource (*iter).row(); + emit editRequest (mModel->getUniversalId (row), mModel->getHint (row)); + } } void CSVTools::ReportTable::removeSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); - std::reverse (selectedRows.begin(), selectedRows.end()); + std::vector rows; - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + for (QModelIndexList::iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) - mModel->removeRows (iter->row(), 1); + { + rows.push_back (mProxyModel->mapToSource (*iter).row()); + } + + std::sort (rows.begin(), rows.end()); + + for (std::vector::const_reverse_iterator iter (rows.rbegin()); iter!=rows.rend(); ++iter) + mProxyModel->removeRows (*iter, 1); selectionModel()->clear(); } diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index e19b327e4..c847b2d47 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -6,6 +6,7 @@ #include "../world/dragrecordtable.hpp" class QAction; +class QSortFilterProxyModel; namespace CSMTools { @@ -30,7 +31,8 @@ namespace CSVTools Action_Remove, Action_EditAndRemove }; - + + QSortFilterProxyModel *mProxyModel; CSMTools::ReportModel *mModel; CSVWorld::CommandDelegate *mIdTypeDelegate; QAction *mShowAction; @@ -63,11 +65,14 @@ namespace CSVTools void clear(); - // Return indices of rows that are suitable for replacement. - // - // \param selection Only list selected rows. + /// Return indices of rows that are suitable for replacement. + /// + /// \param selection Only list selected rows. + /// + /// \return rows in the original model std::vector getReplaceIndices (bool selection) const; + /// \param index row in the original model void flagAsReplaced (int index); private slots: @@ -78,8 +83,8 @@ namespace CSVTools public slots: - void stateChanged (int state, CSMDoc::Document *document); - + void stateChanged (int state, CSMDoc::Document *document); + signals: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); From efed2556befab9947e06cfc0ebc92db804a9a02a Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 15:37:10 +0300 Subject: [PATCH 064/441] Create a context menu handler for dialogue editors with ID information --- apps/opencs/view/world/dialoguesubview.cpp | 69 ++++++++++++++++++++++ apps/opencs/view/world/dialoguesubview.hpp | 24 ++++++++ 2 files changed, 93 insertions(+) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index c157bedc6..5291e3429 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "../../model/world/nestedtableproxymodel.hpp" #include "../../model/world/columnbase.hpp" @@ -314,6 +315,74 @@ CSVWorld::DialogueDelegateDispatcher::~DialogueDelegateDispatcher() } } + +CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display) + : QObject(widget), + mWidget(widget), + mIdType(CSMWorld::TableMimeData::convertEnums(display)) +{ + Q_ASSERT(mWidget != NULL); + Q_ASSERT(CSMWorld::ColumnBase::isId(display)); + Q_ASSERT(mIdType != CSMWorld::UniversalId::Type_None); + + mWidget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(mWidget, + SIGNAL(customContextMenuRequested(const QPoint &)), + this, + SLOT(showContextMenu(const QPoint &))); + + mEditIdAction = new QAction(this); + + QLineEdit *lineEdit = qobject_cast(mWidget); + if (lineEdit != NULL) + { + mContextMenu = lineEdit->createStandardContextMenu(); + mContextMenu->setParent(mWidget); + + QAction *action = mContextMenu->actions().first(); + mContextMenu->insertAction(action, mEditIdAction); + mContextMenu->insertSeparator(action); + } + else + { + mContextMenu = new QMenu(mWidget); + mContextMenu->addAction(mEditIdAction); + } +} + +QString CSVWorld::IdContextMenu::getWidgetValue() const +{ + static QLineEdit *lineEdit = qobject_cast(mWidget); + static QLabel *label = qobject_cast(mWidget); + + QString value = ""; + if (lineEdit != NULL) + { + value = lineEdit->text(); + } + else if (label != NULL) + { + value = label->text(); + } + return value; +} + +void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) +{ + QString value = getWidgetValue(); + if (!value.isEmpty()) + { + mEditIdAction->setText("Edit '" + value + "'"); + + QAction *selectedAction = mContextMenu->exec(mWidget->mapToGlobal(pos)); + if (selectedAction != NULL && selectedAction == mEditIdAction) + { + CSMWorld::UniversalId editId(mIdType, value.toUtf8().constData()); + emit editIdRequest(editId, ""); + } + } +} + /* =============================================================EditWidget===================================================== */ diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index d93a0e73d..5ac7ac20b 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -11,12 +11,14 @@ #include "../../model/world/columnbase.hpp" #include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/universalid.hpp" class QDataWidgetMapper; class QSize; class QEvent; class QLabel; class QVBoxLayout; +class QMenu; namespace CSMWorld { @@ -149,6 +151,28 @@ namespace CSVWorld CSMWorld::ColumnBase::Display display); }; + class IdContextMenu : public QObject + { + Q_OBJECT + + QWidget *mWidget; + CSMWorld::UniversalId::Type mIdType; + + QMenu *mContextMenu; + QAction *mEditIdAction; + + QString getWidgetValue() const; + + public: + IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display); + + private slots: + void showContextMenu(const QPoint &pos); + + signals: + void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); + }; + class EditWidget : public QScrollArea { Q_OBJECT From 814f2d3376c398700aa72b429972241b841498a5 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 16:24:08 +0300 Subject: [PATCH 065/441] Edit 'ID' action is available in dialogue sub-views (for LineEdits and Labels) --- apps/opencs/view/world/dialoguesubview.cpp | 33 ++++++++++++++++------ apps/opencs/view/world/dialoguesubview.hpp | 4 +++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 5291e3429..040ca542a 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -332,6 +332,7 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di SLOT(showContextMenu(const QPoint &))); mEditIdAction = new QAction(this); + connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editIdRequest())); QLineEdit *lineEdit = qobject_cast(mWidget); if (lineEdit != NULL) @@ -352,8 +353,8 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di QString CSVWorld::IdContextMenu::getWidgetValue() const { - static QLineEdit *lineEdit = qobject_cast(mWidget); - static QLabel *label = qobject_cast(mWidget); + QLineEdit *lineEdit = qobject_cast(mWidget); + QLabel *label = qobject_cast(mWidget); QString value = ""; if (lineEdit != NULL) @@ -373,16 +374,16 @@ void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) if (!value.isEmpty()) { mEditIdAction->setText("Edit '" + value + "'"); - - QAction *selectedAction = mContextMenu->exec(mWidget->mapToGlobal(pos)); - if (selectedAction != NULL && selectedAction == mEditIdAction) - { - CSMWorld::UniversalId editId(mIdType, value.toUtf8().constData()); - emit editIdRequest(editId, ""); - } + mContextMenu->exec(mWidget->mapToGlobal(pos)); } } +void CSVWorld::IdContextMenu::editIdRequest() +{ + CSMWorld::UniversalId editId(mIdType, getWidgetValue().toUtf8().constData()); + emit editIdRequest(editId, ""); +} + /* =============================================================EditWidget===================================================== */ @@ -559,6 +560,15 @@ void CSVWorld::EditWidget::remake(int row) editor->setEnabled(false); label->setEnabled(false); } + + if (CSMWorld::ColumnBase::isId(display)) + { + IdContextMenu *menu = new IdContextMenu(editor, display); + connect(menu, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); + } } } else @@ -686,6 +696,11 @@ CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::Universa mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); dataChanged(mTable->getModelIndex (getUniversalId().getId(), 0)); + + connect(mEditWidget, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(focusId(const CSMWorld::UniversalId &, const std::string &))); } void CSVWorld::SimpleDialogueSubView::setEditLock (bool locked) diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 5ac7ac20b..2055e67bd 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -168,6 +168,7 @@ namespace CSVWorld private slots: void showContextMenu(const QPoint &pos); + void editIdRequest(); signals: void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); @@ -195,6 +196,9 @@ namespace CSVWorld virtual ~EditWidget(); void remake(int row); + + signals: + void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; class SimpleDialogueSubView : public CSVDoc::SubView From d777c7a68abd2c1da32de0012883de9978e04463 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 16:51:47 +0300 Subject: [PATCH 066/441] IdContextMenu: if the ID field is empty show the standard context menu (if available) --- apps/opencs/view/world/dialoguesubview.cpp | 48 ++++++++++++++++++---- apps/opencs/view/world/dialoguesubview.hpp | 2 + 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 040ca542a..ee78feac4 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -338,16 +338,10 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di if (lineEdit != NULL) { mContextMenu = lineEdit->createStandardContextMenu(); - mContextMenu->setParent(mWidget); - - QAction *action = mContextMenu->actions().first(); - mContextMenu->insertAction(action, mEditIdAction); - mContextMenu->insertSeparator(action); } else { mContextMenu = new QMenu(mWidget); - mContextMenu->addAction(mEditIdAction); } } @@ -368,12 +362,52 @@ QString CSVWorld::IdContextMenu::getWidgetValue() const return value; } +void CSVWorld::IdContextMenu::addEditIdActionToMenu(const QString &text) +{ + mEditIdAction->setText(text); + if (mContextMenu->actions().isEmpty()) + { + mContextMenu->addAction(mEditIdAction); + } + else + { + QAction *action = mContextMenu->actions().first(); + mContextMenu->insertAction(action, mEditIdAction); + mContextMenu->insertSeparator(action); + } +} + +void CSVWorld::IdContextMenu::removeEditIdActionFromMenu() +{ + if (mContextMenu->actions().isEmpty()) + { + return; + } + + if (mContextMenu->actions().first() == mEditIdAction) + { + mContextMenu->removeAction(mEditIdAction); + if (!mContextMenu->actions().isEmpty() && mContextMenu->actions().first()->isSeparator()) + { + mContextMenu->removeAction(mContextMenu->actions().first()); + } + } +} + void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) { QString value = getWidgetValue(); if (!value.isEmpty()) { - mEditIdAction->setText("Edit '" + value + "'"); + addEditIdActionToMenu("Edit '" + value + "'"); + } + else + { + removeEditIdActionFromMenu(); + } + + if (!mContextMenu->actions().isEmpty()) + { mContextMenu->exec(mWidget->mapToGlobal(pos)); } } diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 2055e67bd..b35f11eb8 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -162,6 +162,8 @@ namespace CSVWorld QAction *mEditIdAction; QString getWidgetValue() const; + void addEditIdActionToMenu(const QString &text); + void removeEditIdActionFromMenu(); public: IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display); From bdb063a69196ad98533e65c002ab2c02f3b3ef4c Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 18:48:13 +0300 Subject: [PATCH 067/441] Prevent the Edit 'ID' action for the current ID of the dialogue subview --- apps/opencs/view/world/dialoguesubview.cpp | 13 ++++++++++++- apps/opencs/view/world/dialoguesubview.hpp | 6 ++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index ee78feac4..88d7b81ce 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -345,6 +345,11 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di } } +void CSVWorld::IdContextMenu::excludeId(const std::string &id) +{ + mExcludedIds.insert(id); +} + QString CSVWorld::IdContextMenu::getWidgetValue() const { QLineEdit *lineEdit = qobject_cast(mWidget); @@ -397,7 +402,8 @@ void CSVWorld::IdContextMenu::removeEditIdActionFromMenu() void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) { QString value = getWidgetValue(); - if (!value.isEmpty()) + bool isExcludedId = mExcludedIds.find(value.toUtf8().constData()) != mExcludedIds.end(); + if (!value.isEmpty() && !isExcludedId) { addEditIdActionToMenu("Edit '" + value + "'"); } @@ -597,7 +603,12 @@ void CSVWorld::EditWidget::remake(int row) if (CSMWorld::ColumnBase::isId(display)) { + int idColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + QString id = mTable->data(mTable->index(row, idColumn)).toString(); + IdContextMenu *menu = new IdContextMenu(editor, display); + // Current ID is already opened, so no need to create Edit 'ID' action for it + menu->excludeId(id.toUtf8().constData()); connect(menu, SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), this, diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index b35f11eb8..bdad39578 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -1,6 +1,7 @@ #ifndef CSV_WORLD_DIALOGUESUBVIEW_H #define CSV_WORLD_DIALOGUESUBVIEW_H +#include #include #include @@ -151,12 +152,15 @@ namespace CSVWorld CSMWorld::ColumnBase::Display display); }; + /// A context menu with "Edit 'ID'" action for editors in the dialogue subview class IdContextMenu : public QObject { Q_OBJECT QWidget *mWidget; CSMWorld::UniversalId::Type mIdType; + std::set mExcludedIds; + ///< A list of IDs that should not have the Edit 'ID' action. QMenu *mContextMenu; QAction *mEditIdAction; @@ -168,6 +172,8 @@ namespace CSVWorld public: IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display); + void excludeId(const std::string &id); + private slots: void showContextMenu(const QPoint &pos); void editIdRequest(); From ba682015932b60fa07769023293b2726b474849d Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 19:07:44 +0300 Subject: [PATCH 068/441] IdContextMenu: don't add Edit action if it's already in the context menu --- apps/opencs/view/world/dialoguesubview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 88d7b81ce..d6ba40b9f 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -374,7 +374,7 @@ void CSVWorld::IdContextMenu::addEditIdActionToMenu(const QString &text) { mContextMenu->addAction(mEditIdAction); } - else + else if (mContextMenu->actions().first() != mEditIdAction) { QAction *action = mContextMenu->actions().first(); mContextMenu->insertAction(action, mEditIdAction); From 110306f6b07ef8a9336b274d3d69c9c0c66bea0f Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 18:49:32 +0300 Subject: [PATCH 069/441] Create a separate class for Edit 'ID' action to use in tables' context menus --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/tableeditidaction.cpp | 42 ++++++++++++++++++++ apps/opencs/view/world/tableeditidaction.hpp | 31 +++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/view/world/tableeditidaction.cpp create mode 100644 apps/opencs/view/world/tableeditidaction.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 02e26c010..ee1d5253d 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -64,7 +64,7 @@ opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable - dialoguespinbox recordbuttonbar + dialoguespinbox recordbuttonbar tableeditidaction ) opencs_units_noqt (view/world diff --git a/apps/opencs/view/world/tableeditidaction.cpp b/apps/opencs/view/world/tableeditidaction.cpp new file mode 100644 index 000000000..7ce726e41 --- /dev/null +++ b/apps/opencs/view/world/tableeditidaction.cpp @@ -0,0 +1,42 @@ +#include "tableeditidaction.hpp" + +#include + +#include "../../model/world/tablemimedata.hpp" + +CSVWorld::TableEditIdAction::CellData CSVWorld::TableEditIdAction::getCellData(int row, int column) const +{ + QModelIndex index = mTable.model()->index(row, column); + if (index.isValid()) + { + QVariant display = mTable.model()->data(index, CSMWorld::ColumnBase::Role_Display); + QString value = mTable.model()->data(index).toString(); + return std::make_pair(static_cast(display.toInt()), value); + } + return std::make_pair(CSMWorld::ColumnBase::Display_None, ""); +} + +CSVWorld::TableEditIdAction::TableEditIdAction(const QTableView &table, QWidget *parent) + : QAction(parent), + mTable(table), + mCurrentId(CSMWorld::UniversalId::Type_None) +{} + +void CSVWorld::TableEditIdAction::setCell(int row, int column) +{ + CellData data = getCellData(row, column); + mCurrentId = CSMWorld::UniversalId(CSMWorld::TableMimeData::convertEnums(data.first), + data.second.toUtf8().constData()); + setText("Edit '" + data.second + "'"); +} + +CSMWorld::UniversalId CSVWorld::TableEditIdAction::getCurrentId() const +{ + return mCurrentId; +} + +bool CSVWorld::TableEditIdAction::isValidIdCell(int row, int column) const +{ + CellData data = getCellData(row, column); + return CSMWorld::ColumnBase::isId(data.first) && !data.second.isEmpty(); +} diff --git a/apps/opencs/view/world/tableeditidaction.hpp b/apps/opencs/view/world/tableeditidaction.hpp new file mode 100644 index 000000000..f2cf0b7bd --- /dev/null +++ b/apps/opencs/view/world/tableeditidaction.hpp @@ -0,0 +1,31 @@ +#ifndef CSVWORLD_TABLEEDITIDACTION_HPP +#define CSVWORLD_TABLEEDITIDACTION_HPP + +#include + +#include "../../model/world/columnbase.hpp" +#include "../../model/world/universalid.hpp" + +class QTableView; + +namespace CSVWorld +{ + class TableEditIdAction : public QAction + { + const QTableView &mTable; + CSMWorld::UniversalId mCurrentId; + + typedef std::pair CellData; + CellData getCellData(int row, int column) const; + + public: + TableEditIdAction(const QTableView &table, QWidget *parent = 0); + + void setCell(int row, int column); + + CSMWorld::UniversalId getCurrentId() const; + bool isValidIdCell(int row, int column) const; + }; +} + +#endif From 561c3bd55343cab079b3b702bc0f5c7ca2fd311b Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 18:51:39 +0300 Subject: [PATCH 070/441] Rework Table (view) code to use a TableEditIdAction --- apps/opencs/view/world/table.cpp | 43 +++++++++----------------------- apps/opencs/view/world/table.hpp | 4 +-- 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 38b38bef9..8146bc427 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -30,6 +30,7 @@ #include "../../model/settings/usersettings.hpp" #include "recordstatusdelegate.hpp" +#include "tableeditidaction.hpp" #include "util.hpp" void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) @@ -61,33 +62,13 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) /// \todo add menu items for select all and clear selection + int currentRow = rowAt(event->y()); + int currentColumn = columnAt(event->x()); + if (mEditIdAction->isValidIdCell(currentRow, currentColumn)) { - // Request UniversalId editing from table columns. - - int currRow = rowAt( event->y() ), - currCol = columnAt( event->x() ); - - currRow = mProxyModel->mapToSource(mProxyModel->index( currRow, 0 )).row(); - - CSMWorld::ColumnBase::Display colDisplay = - static_cast( - mModel->headerData( - currCol, - Qt::Horizontal, - CSMWorld::ColumnBase::Role_Display ).toInt()); - - QString cellData = mModel->data(mModel->index( currRow, currCol )).toString(); - CSMWorld::UniversalId::Type colType = CSMWorld::TableMimeData::convertEnums( colDisplay ); - - if ( !cellData.isEmpty() - && colType != CSMWorld::UniversalId::Type_None ) - { - mEditCellAction->setText(tr("Edit '").append(cellData).append("'")); - - menu.addAction( mEditCellAction ); - - mEditCellId = CSMWorld::UniversalId( colType, cellData.toUtf8().constData() ); - } + mEditIdAction->setCell(currentRow, currentColumn); + menu.addAction(mEditIdAction); + menu.addSeparator(); } if (!mEditLock && !(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) @@ -366,10 +347,6 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); addAction (mMoveDownAction); - mEditCellAction = new QAction( tr("Edit Cell"), this ); - connect( mEditCellAction, SIGNAL(triggered()), this, SLOT(editCell()) ); - addAction( mEditCellAction ); - mViewAction = new QAction (tr ("View"), this); connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord())); addAction (mViewAction); @@ -390,6 +367,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mExtendedRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedRevert())); addAction (mExtendedRevertAction); + mEditIdAction = new TableEditIdAction (*this, this); + connect (mEditIdAction, SIGNAL (triggered()), this, SLOT (editCell())); + addAction (mEditIdAction); + connect (mProxyModel, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); @@ -543,7 +524,7 @@ void CSVWorld::Table::moveDownRecord() void CSVWorld::Table::editCell() { - emit editRequest( mEditCellId, std::string() ); + emit editRequest(mEditIdAction->getCurrentId(), ""); } void CSVWorld::Table::viewRecord() diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 20053ccd5..530b190e5 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -30,6 +30,7 @@ namespace CSMWorld namespace CSVWorld { class CommandDelegate; + class TableEditIdAction; ///< Table widget class Table : public DragRecordTable @@ -57,15 +58,14 @@ namespace CSVWorld QAction *mMoveUpAction; QAction *mMoveDownAction; QAction *mViewAction; - QAction *mEditCellAction; QAction *mPreviewAction; QAction *mExtendedDeleteAction; QAction *mExtendedRevertAction; + TableEditIdAction *mEditIdAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTableBase *mModel; int mRecordStatusDisplay; CSMWorld::CommandDispatcher *mDispatcher; - CSMWorld::UniversalId mEditCellId; std::map mDoubleClickActions; bool mJumpToAddedRecord; bool mUnselectAfterJump; From 09a95f276f905dabedae1a2cb2dc73cde5954afd Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 19:27:42 +0300 Subject: [PATCH 071/441] Add Edit 'ID' action for nested tables Conflicts: apps/opencs/view/world/nestedtable.cpp --- apps/opencs/view/world/dialoguesubview.cpp | 5 ++++ apps/opencs/view/world/nestedtable.cpp | 35 +++++++++++++++++----- apps/opencs/view/world/nestedtable.hpp | 8 +++++ 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index d6ba40b9f..aa321c42e 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -567,6 +567,11 @@ void CSVWorld::EditWidget::remake(int row) tablesLayout->addWidget(label); tablesLayout->addWidget(table); + + connect(table, + SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); } else if (!(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) { diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index e4447397d..de3b3aa16 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -1,23 +1,25 @@ #include "nestedtable.hpp" -#include "../../model/world/nestedtableproxymodel.hpp" -#include "../../model/world/universalid.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/commanddispatcher.hpp" -#include "util.hpp" #include #include #include #include +#include "../../model/world/nestedtableproxymodel.hpp" +#include "../../model/world/universalid.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/commanddispatcher.hpp" + +#include "tableeditidaction.hpp" +#include "util.hpp" + CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, QWidget* parent, bool editable) : DragRecordTable(document, parent), - mAddNewRowAction(0), - mRemoveRowAction(0), + mEditIdAction(0), mModel(model) { setSelectionBehavior (QAbstractItemView::SelectRows); @@ -61,6 +63,9 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, connect(mRemoveRowAction, SIGNAL(triggered()), this, SLOT(removeRowActionTriggered())); + + mEditIdAction = new TableEditIdAction(*this, this); + connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editCell())); } } @@ -72,13 +77,22 @@ std::vector CSVWorld::NestedTable::getDraggedRecords() co void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) { - if (!mRemoveRowAction || !mAddNewRowAction) + if (!mEditIdAction) return; QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu(this); + int currentRow = rowAt(event->y()); + int currentColumn = columnAt(event->x()); + if (mEditIdAction->isValidIdCell(currentRow, currentColumn)) + { + mEditIdAction->setCell(currentRow, currentColumn); + menu.addAction(mEditIdAction); + menu.addSeparator(); + } + if (selectionModel()->selectedRows().size() == 1) menu.addAction(mRemoveRowAction); @@ -102,3 +116,8 @@ void CSVWorld::NestedTable::addNewRowActionTriggered() selectionModel()->selectedRows().size(), mModel->getParentColumn())); } + +void CSVWorld::NestedTable::editCell() +{ + emit editRequest(mEditIdAction->getCurrentId(), ""); +} diff --git a/apps/opencs/view/world/nestedtable.hpp b/apps/opencs/view/world/nestedtable.hpp index 70008fa97..23a925dca 100644 --- a/apps/opencs/view/world/nestedtable.hpp +++ b/apps/opencs/view/world/nestedtable.hpp @@ -22,12 +22,15 @@ namespace CSMDoc namespace CSVWorld { + class TableEditIdAction; + class NestedTable : public DragRecordTable { Q_OBJECT QAction *mAddNewRowAction; QAction *mRemoveRowAction; + TableEditIdAction *mEditIdAction; CSMWorld::NestedTableProxyModel* mModel; CSMWorld::CommandDispatcher *mDispatcher; @@ -47,6 +50,11 @@ namespace CSVWorld void removeRowActionTriggered(); void addNewRowActionTriggered(); + + void editCell(); + + signals: + void editRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; } From bcd0f0f4e4d8b1dc4682f03648c509b5ed1d1028 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 19:39:53 +0300 Subject: [PATCH 072/441] TableEditIdAction: prevent creation of a UniversalId with Type_None --- apps/opencs/view/world/tableeditidaction.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/world/tableeditidaction.cpp b/apps/opencs/view/world/tableeditidaction.cpp index 7ce726e41..4dfc537cc 100644 --- a/apps/opencs/view/world/tableeditidaction.cpp +++ b/apps/opencs/view/world/tableeditidaction.cpp @@ -25,9 +25,13 @@ CSVWorld::TableEditIdAction::TableEditIdAction(const QTableView &table, QWidget void CSVWorld::TableEditIdAction::setCell(int row, int column) { CellData data = getCellData(row, column); - mCurrentId = CSMWorld::UniversalId(CSMWorld::TableMimeData::convertEnums(data.first), - data.second.toUtf8().constData()); - setText("Edit '" + data.second + "'"); + CSMWorld::UniversalId::Type idType = CSMWorld::TableMimeData::convertEnums(data.first); + + if (idType != CSMWorld::UniversalId::Type_None) + { + mCurrentId = CSMWorld::UniversalId(idType, data.second.toUtf8().constData()); + setText("Edit '" + data.second + "'"); + } } CSMWorld::UniversalId CSVWorld::TableEditIdAction::getCurrentId() const @@ -38,5 +42,8 @@ CSMWorld::UniversalId CSVWorld::TableEditIdAction::getCurrentId() const bool CSVWorld::TableEditIdAction::isValidIdCell(int row, int column) const { CellData data = getCellData(row, column); - return CSMWorld::ColumnBase::isId(data.first) && !data.second.isEmpty(); + CSMWorld::UniversalId::Type idType = CSMWorld::TableMimeData::convertEnums(data.first); + return CSMWorld::ColumnBase::isId(data.first) && + idType != CSMWorld::UniversalId::Type_None && + !data.second.isEmpty(); } From 27c9eaeffcbe11db563ea187315ddd8e572a9d44 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 19:55:48 +0300 Subject: [PATCH 073/441] Add Edit 'ID' action for nested fields --- apps/opencs/view/world/dialoguesubview.cpp | 38 ++++++++++++++-------- apps/opencs/view/world/dialoguesubview.hpp | 3 ++ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index aa321c42e..dfe8e0529 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -428,6 +428,28 @@ void CSVWorld::IdContextMenu::editIdRequest() =============================================================EditWidget===================================================== */ +void CSVWorld::EditWidget::createEditorContextMenu(QWidget *editor, + CSMWorld::ColumnBase::Display display, + int currentRow) const +{ + Q_ASSERT(editor != NULL); + + if (CSMWorld::ColumnBase::isId(display) && + CSMWorld::TableMimeData::convertEnums(display) != CSMWorld::UniversalId::Type_None) + { + int idColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + QString id = mTable->data(mTable->index(currentRow, idColumn)).toString(); + + IdContextMenu *menu = new IdContextMenu(editor, display); + // Current ID is already opened, so no need to create Edit 'ID' action for it + menu->excludeId(id.toUtf8().constData()); + connect(menu, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); + } +} + CSVWorld::EditWidget::~EditWidget() { for (unsigned i = 0; i < mNestedModels.size(); ++i) @@ -606,19 +628,7 @@ void CSVWorld::EditWidget::remake(int row) label->setEnabled(false); } - if (CSMWorld::ColumnBase::isId(display)) - { - int idColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_Id); - QString id = mTable->data(mTable->index(row, idColumn)).toString(); - - IdContextMenu *menu = new IdContextMenu(editor, display); - // Current ID is already opened, so no need to create Edit 'ID' action for it - menu->excludeId(id.toUtf8().constData()); - connect(menu, - SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), - this, - SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); - } + createEditorContextMenu(editor, display, row); } } else @@ -670,6 +680,8 @@ void CSVWorld::EditWidget::remake(int row) editor->setEnabled(false); label->setEnabled(false); } + + createEditorContextMenu(editor, display, row); } } mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i))); diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index bdad39578..1888a6572 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -195,6 +195,9 @@ namespace CSVWorld CSMDoc::Document& mDocument; std::vector mNestedModels; //Plain, raw C pointers, deleted in the dtor + void createEditorContextMenu(QWidget *editor, + CSMWorld::ColumnBase::Display display, + int currentRow) const; public: EditWidget (QWidget *parent, int row, CSMWorld::IdTable* table, From fb395e4dc9e02db26637363f357c2a45723a7684 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 5 Jul 2015 15:36:41 +0300 Subject: [PATCH 074/441] Count of nested columns can be retrieved for a collection with no records --- apps/opencs/model/world/nestedidcollection.hpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/nestedidcollection.hpp b/apps/opencs/model/world/nestedidcollection.hpp index 792a13b7d..56b112365 100644 --- a/apps/opencs/model/world/nestedidcollection.hpp +++ b/apps/opencs/model/world/nestedidcollection.hpp @@ -161,8 +161,19 @@ namespace CSMWorld template int NestedIdCollection::getNestedColumnsCount(int row, int column) const { - return getAdapter(Collection::getColumn(column)).getColumnsCount( - Collection::getRecord(row)); + const ColumnBase &nestedColumn = Collection::getColumn(column); + int numRecords = Collection::getSize(); + if (row >= 0 && row < numRecords) + { + const Record& record = Collection::getRecord(row); + return getAdapter(nestedColumn).getColumnsCount(record); + } + else + { + // If the row is invalid (or there no records), retrieve the column count using a blank record + const Record record; + return getAdapter(nestedColumn).getColumnsCount(record); + } } template From 5dc87e50088ad69a9f29b4747f4c651f27bb63c9 Mon Sep 17 00:00:00 2001 From: MiroslavR Date: Mon, 6 Jul 2015 16:35:45 +0200 Subject: [PATCH 075/441] Fix calculation of selected blocks in ScriptEdit --- apps/opencs/view/world/scriptedit.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index ad2cddbf8..25f4fd077 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -276,11 +276,11 @@ void CSVWorld::ScriptEdit::lineNumberAreaPaintEvent(QPaintEvent *event) if(textCursor().hasSelection()) { QString str = textCursor().selection().toPlainText(); - int selectedLines = str.count("\n")+1; + int offset = str.count("\n"); if(textCursor().position() < textCursor().anchor()) - endBlock += selectedLines; + endBlock += offset; else - startBlock -= selectedLines; + startBlock -= offset; } painter.setBackgroundMode(Qt::OpaqueMode); QFont font = painter.font(); From 0671987d10fbd1deb28fc611159607912fa26b06 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 18 Jul 2015 01:38:35 +0200 Subject: [PATCH 076/441] Fix config file priority in the launcher to match OpenMW --- apps/launcher/maindialog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 2f7a9db33..304bf45ea 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -309,11 +309,11 @@ bool Launcher::MainDialog::setupGameSettings() mGameSettings.readUserFile(stream); } - // Now the rest + // Now the rest - priority: user > local > global QStringList paths; - paths.append(userPath + QString("openmw.cfg")); - paths.append(QString("openmw.cfg")); paths.append(globalPath + QString("openmw.cfg")); + paths.append(QString("openmw.cfg")); + paths.append(userPath + QString("openmw.cfg")); foreach (const QString &path, paths) { qDebug() << "Loading config file:" << qPrintable(path); From ee85bbc0e6e541b01001dc534dd5f7d3b307f21c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 11 Jul 2015 16:09:13 +0200 Subject: [PATCH 077/441] added button bar to script subview --- apps/opencs/view/world/scriptsubview.cpp | 61 ++++++++++++++++++------ apps/opencs/view/world/scriptsubview.hpp | 7 +++ 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 411eb3660..dc079c3a9 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -15,26 +15,20 @@ #include "../../model/settings/usersettings.hpp" #include "scriptedit.hpp" +#include "recordbuttonbar.hpp" CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mStatus(0) +: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mStatus(0), + mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) { - QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); + std::vector selection (1, id.getId()); + mCommandDispatcher.setSelection (selection); - mBottom = new QWidget(this); - QStackedLayout *bottmLayout = new QStackedLayout(mBottom); - bottmLayout->setContentsMargins (0, 0, 0, 0); - QStatusBar *statusBar = new QStatusBar(mBottom); - mStatus = new QLabel(mBottom); - statusBar->addWidget (mStatus); - bottmLayout->addWidget (statusBar); - mBottom->setLayout (bottmLayout); + QVBoxLayout *layout = new QVBoxLayout; - layout->addWidget (mBottom, 0); - layout->insertWidget (0, mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this), 2); + layout->addWidget (mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this), 2); - QWidget *widget = new QWidget; + QWidget *widget = new QWidget (this);; widget->setLayout (layout); setWidget (widget); @@ -54,6 +48,25 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: mEditor->setPlainText (mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString()); + // buttons + mButtons = new RecordButtonBar (id, *mModel, 0, &mCommandDispatcher, this); + + layout->addWidget (mButtons); + + // status bar + QStatusBar *statusBar = new QStatusBar(mBottom); + mStatus = new QLabel(mBottom); + statusBar->addWidget (mStatus); + + mBottom = new QWidget(this); + QStackedLayout *bottmLayout = new QStackedLayout(mBottom); + bottmLayout->setContentsMargins (0, 0, 0, 0); + bottmLayout->addWidget (statusBar); + mBottom->setLayout (bottmLayout); + + layout->addWidget (mBottom, 0); + + // signals connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged())); connect (mModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), @@ -62,6 +75,11 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: connect (mModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (rowsAboutToBeRemoved (const QModelIndex&, int, int))); + connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); + + connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), + mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); + updateStatusBar(); connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar())); } @@ -78,6 +96,8 @@ void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStr { mEditor->setMonoFont(value.at(0).toStdString() == "true"); } + + mButtons->updateUserSetting (name, value); } void CSVWorld::ScriptSubView::updateStatusBar () @@ -93,6 +113,8 @@ void CSVWorld::ScriptSubView::updateStatusBar () void CSVWorld::ScriptSubView::setEditLock (bool locked) { mEditor->setReadOnly (locked); + mButtons->setEditLock (locked); + mCommandDispatcher.setEditLock (locked); } void CSVWorld::ScriptSubView::useHint (const std::string& hint) @@ -159,3 +181,14 @@ void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, i emit closeRequest(); } +void CSVWorld::ScriptSubView::switchToRow (int row) +{ + int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + std::string id = mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData(); + setUniversalId (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, id)); + + mEditor->setPlainText (mModel->data (mModel->index (row, mColumn)).toString()); + + std::vector selection (1, id); + mCommandDispatcher.setSelection (selection); +} diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 1c6474e54..0479e6ad8 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -1,6 +1,8 @@ #ifndef CSV_WORLD_SCRIPTSUBVIEW_H #define CSV_WORLD_SCRIPTSUBVIEW_H +#include "../../model/world/commanddispatcher.hpp" + #include "../doc/subview.hpp" class QModelIndex; @@ -19,6 +21,7 @@ namespace CSMWorld namespace CSVWorld { class ScriptEdit; + class RecordButtonBar; class ScriptSubView : public CSVDoc::SubView { @@ -30,6 +33,8 @@ namespace CSVWorld int mColumn; QWidget *mBottom; QLabel *mStatus; + RecordButtonBar *mButtons; + CSMWorld::CommandDispatcher mCommandDispatcher; public: @@ -52,6 +57,8 @@ namespace CSVWorld private slots: void updateStatusBar(); + + void switchToRow (int row); }; } From 05ed8ada11dde8b8abadf57274a07618f2f925ef Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 13 Jul 2015 09:42:16 +0200 Subject: [PATCH 078/441] renaming a few user settings categories --- apps/opencs/model/settings/usersettings.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 35a0a26ba..2640ed549 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -189,7 +189,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() ritd->setDeclaredValues (values); } - declareSection ("table-input", "Table Input"); + declareSection ("table-input", "ID Tables"); { QString inPlaceEdit ("Edit in Place"); QString editRecord ("Edit Record"); @@ -244,7 +244,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() jumpToAdded->setDeclaredValues (jumpValues); } - declareSection ("report-input", "Report Input"); + declareSection ("report-input", "Reports"); { QString none ("None"); QString edit ("Edit"); @@ -284,7 +284,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() shiftCtrlDoubleClick->setDefaultValue (none); shiftCtrlDoubleClick->setToolTip ("Action on shift control double click in report table:

" + toolTip); } - + declareSection ("search", "Search & Replace"); { Setting *before = createSetting (Type_SpinBox, "char-before", @@ -326,7 +326,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() QStringList modes; modes << "Ignore" << modeNormal << "Strict"; - + Setting *warnings = createSetting (Type_ComboBox, "warnings", "Warning Mode"); warnings->setDeclaredValues (modes); @@ -336,7 +336,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() "

  • Normal: Report warning as a warning
  • " "
  • Strict: Promote warning to an error
  • " ""); - + Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int"); formatInt->setDefaultValues (QStringList() << "Dark magenta"); formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); From ab0b5932f76ff57b79e894567d9a2d9bde8ffd12 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 13 Jul 2015 12:52:18 +0200 Subject: [PATCH 079/441] added user settings option to toggle toolbars in single-record subviews Conflicts: apps/opencs/view/world/dialoguesubview.cpp --- apps/opencs/model/settings/usersettings.cpp | 9 +++ apps/opencs/view/world/dialoguesubview.cpp | 56 ++++++++++++++---- apps/opencs/view/world/dialoguesubview.hpp | 16 ++++-- apps/opencs/view/world/scriptsubview.cpp | 63 +++++++++++++++------ apps/opencs/view/world/scriptsubview.hpp | 8 +++ 5 files changed, 116 insertions(+), 36 deletions(-) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 2640ed549..aa6730357 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -244,6 +244,12 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() jumpToAdded->setDeclaredValues (jumpValues); } + declareSection ("dialogues", "ID Dialogues"); + { + Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar"); + toolbar->setDefaultValue ("true"); + } + declareSection ("report-input", "Reports"); { QString none ("None"); @@ -337,6 +343,9 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() "
  • Strict: Promote warning to an error
  • " ""); + Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar"); + toolbar->setDefaultValue ("true"); + Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int"); formatInt->setDefaultValues (QStringList() << "Dark magenta"); formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index dfe8e0529..fceb836e7 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -31,6 +31,7 @@ #include "../../model/world/idtree.hpp" #include "../../model/world/commands.hpp" #include "../../model/doc/document.hpp" +#include "../../model/settings/usersettings.hpp" #include "../widget/coloreditor.hpp" #include "../widget/droplineedit.hpp" @@ -863,9 +864,28 @@ void CSVWorld::SimpleDialogueSubView::refreshNpcDialogue (int type, const std::s } } +void CSVWorld::DialogueSubView::addButtonBar() +{ + if (mButtons) + return; + + mButtons = new RecordButtonBar (getUniversalId(), getTable(), mBottom, + &getCommandDispatcher(), this); + + getMainLayout().insertWidget (1, mButtons); + + // connections + connect (mButtons, SIGNAL (showPreview()), this, SLOT (showPreview())); + connect (mButtons, SIGNAL (viewRecord()), this, SLOT (viewRecord())); + connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); + + connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), + mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); +} + CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) -: SimpleDialogueSubView (id, document) +: SimpleDialogueSubView (id, document), mButtons (0) { // bottom box mBottom = new TableBottomBox (creatorFactory, document, id, this); @@ -876,31 +896,43 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, this, SLOT (requestFocus (const std::string&))); // button bar - mButtons = new RecordButtonBar (id, getTable(), mBottom, - &getCommandDispatcher(), this); + if (CSMSettings::UserSettings::instance().setting ("dialogues/toolbar", QString("true")) == "true") + addButtonBar(); // layout - getMainLayout().addWidget (mButtons); getMainLayout().addWidget (mBottom); - - // connections - connect (mButtons, SIGNAL (showPreview()), this, SLOT (showPreview())); - connect (mButtons, SIGNAL (viewRecord()), this, SLOT (viewRecord())); - connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); - - connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), - mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); } void CSVWorld::DialogueSubView::setEditLock (bool locked) { SimpleDialogueSubView::setEditLock (locked); + + if (mButtons) mButtons->setEditLock (locked); } void CSVWorld::DialogueSubView::updateUserSetting (const QString& name, const QStringList& value) { SimpleDialogueSubView::updateUserSetting (name, value); + + if (name=="dialogues/toolbar") + { + if (value.at(0)==QString ("true")) + { + addButtonBar(); + } + else + { + if (mButtons) + { + getMainLayout().removeWidget (mButtons); + delete mButtons; + mButtons = 0; + } + } + } + + if (mButtons) mButtons->updateUserSetting (name, value); } diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 1888a6572..b5c377c50 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -195,8 +195,8 @@ namespace CSVWorld CSMDoc::Document& mDocument; std::vector mNestedModels; //Plain, raw C pointers, deleted in the dtor - void createEditorContextMenu(QWidget *editor, - CSMWorld::ColumnBase::Display display, + void createEditorContextMenu(QWidget *editor, + CSMWorld::ColumnBase::Display display, int currentRow) const; public: @@ -236,7 +236,7 @@ namespace CSVWorld void updateCurrentId(); bool isLocked() const; - + public: SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); @@ -258,10 +258,14 @@ namespace CSVWorld class DialogueSubView : public SimpleDialogueSubView { Q_OBJECT - + TableBottomBox* mBottom; RecordButtonBar *mButtons; + private: + + void addButtonBar(); + public: DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, @@ -270,14 +274,14 @@ namespace CSVWorld virtual void setEditLock (bool locked); virtual void updateUserSetting (const QString& name, const QStringList& value); - + private slots: void showPreview(); void viewRecord(); - void switchToRow (int row); + void switchToRow (int row); void requestFocus (const std::string& id); }; diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index dc079c3a9..0f5d5014a 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -17,19 +17,32 @@ #include "scriptedit.hpp" #include "recordbuttonbar.hpp" +void CSVWorld::ScriptSubView::addButtonBar() +{ + if (mButtons) + return; + + mButtons = new RecordButtonBar (getUniversalId(), *mModel, 0, &mCommandDispatcher, this); + + mLayout.insertWidget (1, mButtons); + + connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); + + connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), + mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); +} + CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mStatus(0), +: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mStatus(0), mButtons (0), mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) { std::vector selection (1, id.getId()); mCommandDispatcher.setSelection (selection); - QVBoxLayout *layout = new QVBoxLayout; - - layout->addWidget (mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this), 2); + mLayout.addWidget (mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this), 2); QWidget *widget = new QWidget (this);; - widget->setLayout (layout); + widget->setLayout (&mLayout); setWidget (widget); mModel = &dynamic_cast ( @@ -49,9 +62,8 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: mEditor->setPlainText (mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString()); // buttons - mButtons = new RecordButtonBar (id, *mModel, 0, &mCommandDispatcher, this); - - layout->addWidget (mButtons); + if (CSMSettings::UserSettings::instance().setting ("script-editor/toolbar", QString("true")) == "true") + addButtonBar(); // status bar QStatusBar *statusBar = new QStatusBar(mBottom); @@ -64,7 +76,7 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: bottmLayout->addWidget (statusBar); mBottom->setLayout (bottmLayout); - layout->addWidget (mBottom, 0); + mLayout.addWidget (mBottom, 0); // signals connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged())); @@ -75,11 +87,6 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: connect (mModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (rowsAboutToBeRemoved (const QModelIndex&, int, int))); - connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); - - connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), - mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); - updateStatusBar(); connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar())); } @@ -88,16 +95,33 @@ void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStr { if (name == "script-editor/show-linenum") { - std::string showLinenum = value.at(0).toStdString(); + std::string showLinenum = value.at(0).toUtf8().constData(); mEditor->showLineNum(showLinenum == "true"); mBottom->setVisible(showLinenum == "true"); } else if (name == "script-editor/mono-font") { - mEditor->setMonoFont(value.at(0).toStdString() == "true"); + mEditor->setMonoFont (value.at(0)==QString ("true")); + } + else if (name=="script-editor/toolbar") + { + if (value.at(0)==QString ("true")) + { + addButtonBar(); + } + else + { + if (mButtons) + { + mLayout.removeWidget (mButtons); + delete mButtons; + mButtons = 0; + } + } } - mButtons->updateUserSetting (name, value); + if (mButtons) + mButtons->updateUserSetting (name, value); } void CSVWorld::ScriptSubView::updateStatusBar () @@ -113,7 +137,10 @@ void CSVWorld::ScriptSubView::updateStatusBar () void CSVWorld::ScriptSubView::setEditLock (bool locked) { mEditor->setReadOnly (locked); - mButtons->setEditLock (locked); + + if (mButtons) + mButtons->setEditLock (locked); + mCommandDispatcher.setEditLock (locked); } diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 0479e6ad8..370754ebe 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -1,12 +1,15 @@ #ifndef CSV_WORLD_SCRIPTSUBVIEW_H #define CSV_WORLD_SCRIPTSUBVIEW_H +#include + #include "../../model/world/commanddispatcher.hpp" #include "../doc/subview.hpp" class QModelIndex; class QLabel; +class QVBoxLayout; namespace CSMDoc { @@ -35,6 +38,11 @@ namespace CSVWorld QLabel *mStatus; RecordButtonBar *mButtons; CSMWorld::CommandDispatcher mCommandDispatcher; + QVBoxLayout mLayout; + + private: + + void addButtonBar(); public: From 286f1c8c5c2ee6b069e2542e70a2527bb5981179 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 14 Jul 2015 10:05:45 +0200 Subject: [PATCH 080/441] replaced the script subview status bar with a bottom box (including a status bar) Conflicts: apps/opencs/view/world/dialoguesubview.cpp --- apps/opencs/view/world/dialoguesubview.cpp | 2 -- apps/opencs/view/world/scriptsubview.cpp | 33 +++++++++----------- apps/opencs/view/world/scriptsubview.hpp | 6 ++-- apps/opencs/view/world/tablebottombox.cpp | 36 ++++++++++++++++++---- apps/opencs/view/world/tablebottombox.hpp | 13 ++++++-- 5 files changed, 59 insertions(+), 31 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index fceb836e7..27b35c17e 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -890,8 +890,6 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, // bottom box mBottom = new TableBottomBox (creatorFactory, document, id, this); - mBottom->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - connect (mBottom, SIGNAL (requestFocus (const std::string&)), this, SLOT (requestFocus (const std::string&))); diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 0f5d5014a..f65f77285 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -4,7 +4,6 @@ #include #include -#include #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" @@ -16,6 +15,8 @@ #include "scriptedit.hpp" #include "recordbuttonbar.hpp" +#include "tablebottombox.hpp" +#include "genericcreator.hpp" void CSVWorld::ScriptSubView::addButtonBar() { @@ -33,7 +34,7 @@ void CSVWorld::ScriptSubView::addButtonBar() } CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mStatus(0), mButtons (0), +: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) { std::vector selection (1, id.getId()); @@ -65,18 +66,13 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: if (CSMSettings::UserSettings::instance().setting ("script-editor/toolbar", QString("true")) == "true") addButtonBar(); - // status bar - QStatusBar *statusBar = new QStatusBar(mBottom); - mStatus = new QLabel(mBottom); - statusBar->addWidget (mStatus); + // bottom box + mBottom = new TableBottomBox (CreatorFactory(), document, id, this); - mBottom = new QWidget(this); - QStackedLayout *bottmLayout = new QStackedLayout(mBottom); - bottmLayout->setContentsMargins (0, 0, 0, 0); - bottmLayout->addWidget (statusBar); - mBottom->setLayout (bottmLayout); + connect (mBottom, SIGNAL (requestFocus (const std::string&)), + this, SLOT (requestFocus (const std::string&))); - mLayout.addWidget (mBottom, 0); + mLayout.addWidget (mBottom); // signals connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged())); @@ -124,14 +120,15 @@ void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStr mButtons->updateUserSetting (name, value); } -void CSVWorld::ScriptSubView::updateStatusBar () +void CSVWorld::ScriptSubView::setStatusBar (bool show) { - std::ostringstream stream; - - stream << "(" << mEditor->textCursor().blockNumber() + 1 << ", " - << mEditor->textCursor().columnNumber() + 1 << ")"; + mBottom->setStatusBar (show); +} - mStatus->setText (QString::fromUtf8 (stream.str().c_str())); +void CSVWorld::ScriptSubView::updateStatusBar () +{ + mBottom->positionChanged (mEditor->textCursor().blockNumber() + 1, + mEditor->textCursor().columnNumber() + 1); } void CSVWorld::ScriptSubView::setEditLock (bool locked) diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 370754ebe..09f7907ee 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -25,6 +25,7 @@ namespace CSVWorld { class ScriptEdit; class RecordButtonBar; + class TableBottomBox; class ScriptSubView : public CSVDoc::SubView { @@ -34,8 +35,7 @@ namespace CSVWorld CSMDoc::Document& mDocument; CSMWorld::IdTable *mModel; int mColumn; - QWidget *mBottom; - QLabel *mStatus; + TableBottomBox *mBottom; RecordButtonBar *mButtons; CSMWorld::CommandDispatcher mCommandDispatcher; QVBoxLayout mLayout; @@ -54,6 +54,8 @@ namespace CSVWorld virtual void updateUserSetting (const QString& name, const QStringList& value); + virtual void setStatusBar (bool show); + public slots: void textChanged(); diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index dc3a6cc76..12226450b 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -35,15 +35,23 @@ void CSVWorld::TableBottomBox::updateStatus() } } + if (mHasPosition) + { + if (!first) + stream << " -- "; + + stream << "(" << mRow << ", " << mColumn << ")"; + } + mStatus->setText (QString::fromUtf8 (stream.str().c_str())); } } -CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, - CSMDoc::Document& document, - const CSMWorld::UniversalId& id, +CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, + CSMDoc::Document& document, + const CSMWorld::UniversalId& id, QWidget *parent) -: QWidget (parent), mShowStatusBar (false), mCreating (false) +: QWidget (parent), mShowStatusBar (false), mCreating (false), mHasPosition (false) { for (int i=0; i<4; ++i) mStatusCount[i] = 0; @@ -74,6 +82,8 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto connect (mCreator, SIGNAL (requestFocus (const std::string&)), this, SIGNAL (requestFocus (const std::string&))); } + + setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Fixed); } void CSVWorld::TableBottomBox::setEditLock (bool locked) @@ -152,6 +162,20 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi updateStatus(); } +void CSVWorld::TableBottomBox::positionChanged (int row, int column) +{ + mRow = row; + mColumn = column; + mHasPosition = true; + updateStatus(); +} + +void CSVWorld::TableBottomBox::noMorePosition() +{ + mHasPosition = false; + updateStatus(); +} + void CSVWorld::TableBottomBox::createRequest() { mCreator->reset(); @@ -162,8 +186,8 @@ void CSVWorld::TableBottomBox::createRequest() mCreator->focus(); } -void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, - const CSMWorld::UniversalId::Type type) +void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, + const CSMWorld::UniversalId::Type type) { mCreator->reset(); mCreator->cloneMode(id, type); diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index a7d009c42..6e68553bc 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -30,6 +30,9 @@ namespace CSVWorld Creator *mCreator; bool mCreating; QStackedLayout *mLayout; + bool mHasPosition; + int mRow; + int mColumn; private: @@ -41,9 +44,9 @@ namespace CSVWorld public: - TableBottomBox (const CreatorFactoryBase& creatorFactory, - CSMDoc::Document& document, - const CSMWorld::UniversalId& id, + TableBottomBox (const CreatorFactoryBase& creatorFactory, + CSMDoc::Document& document, + const CSMWorld::UniversalId& id, QWidget *parent = 0); virtual ~TableBottomBox(); @@ -77,6 +80,10 @@ namespace CSVWorld /// \param deleted Number of deleted records /// \param modified Number of added and modified records + void positionChanged (int row, int column); + + void noMorePosition(); + void createRequest(); void cloneRequest(const std::string& id, const CSMWorld::UniversalId::Type type); From a658efe557045b89bb958e999389bfa6b7ba2a68 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 14 Jul 2015 11:49:41 +0200 Subject: [PATCH 081/441] improving consistency of subview layouts --- apps/opencs/view/tools/searchsubview.cpp | 20 +++++++++----------- apps/opencs/view/world/previewsubview.cpp | 2 -- apps/opencs/view/world/scenesubview.cpp | 2 -- apps/opencs/view/world/tablesubview.cpp | 3 --- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index dc670af40..8b35db6ae 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -16,7 +16,7 @@ void CSVTools::SearchSubView::replace (bool selection) { if (mLocked) return; - + std::vector indices = mTable->getReplaceIndices (selection); std::string replace = mSearchBox.getReplaceText(); @@ -29,7 +29,7 @@ void CSVTools::SearchSubView::replace (bool selection) CSMTools::Search search (mSearch); CSMWorld::IdTableBase *currentTable = 0; - + // We are running through the indices in reverse order to avoid messing up multiple results // in a single string. for (std::vector::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter) @@ -46,7 +46,7 @@ void CSVTools::SearchSubView::replace (bool selection) search.configure (table); currentTable = table; } - + std::string hint = model.getHint (*iter); if (search.verify (mDocument, table, id, hint)) @@ -63,7 +63,7 @@ void CSVTools::SearchSubView::replace (bool selection) void CSVTools::SearchSubView::showEvent (QShowEvent *event) { CSVDoc::SubView::showEvent (event); - mSearchBox.focus(); + mSearchBox.focus(); } CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) @@ -71,25 +71,23 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout->addWidget (&mSearchBox); - + layout->addWidget (mTable = new ReportTable (document, id, true), 2); QWidget *widget = new QWidget; - + widget->setLayout (layout); setWidget (widget); stateChanged (document.getState(), &document); - + connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); connect (mTable, SIGNAL (replaceRequest()), this, SLOT (replaceRequest())); - + connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (stateChanged (int, CSMDoc::Document *))); @@ -124,7 +122,7 @@ void CSVTools::SearchSubView::startSearch (const CSMTools::Search& search) mSearch = search; mSearch.setPadding (paddingBefore, paddingAfter); - + mTable->clear(); mDocument.runSearch (getUniversalId(), mSearch); } diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index 1c2d6b95c..756e79fe6 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -13,8 +13,6 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo { QHBoxLayout *layout = new QHBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - if (document.getData().getReferenceables().searchId (id.getId())==-1) { std::string referenceableId = diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 397d24929..b7a795e23 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -31,8 +31,6 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout->addWidget (mBottom = new TableBottomBox (NullCreatorFactory(), document, id, this), 0); mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 8ffcb620c..ca551c53f 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -26,8 +26,6 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout->addWidget (mBottom = new TableBottomBox (creatorFactory, document, id, this), 0); @@ -185,4 +183,3 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) } return false; } - From 2064a3979bc3ddb3aacd814d5132251e6c3df291 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 14 Jul 2015 13:34:13 +0200 Subject: [PATCH 082/441] fixed deleted button sensitivity state --- apps/opencs/view/world/recordbuttonbar.cpp | 29 +++++++++++----------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp index 63c0dd0a1..9cae0d0c9 100644 --- a/apps/opencs/view/world/recordbuttonbar.cpp +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -17,18 +17,17 @@ void CSVWorld::RecordButtonBar::updateModificationButtons() mCloneButton->setDisabled (createAndDeleteDisabled); mAddButton->setDisabled (createAndDeleteDisabled); - mDeleteButton->setDisabled (createAndDeleteDisabled); bool commandDisabled = !mCommandDispatcher || mLocked; - + mRevertButton->setDisabled (commandDisabled); - mDeleteButton->setDisabled (commandDisabled); + mDeleteButton->setDisabled (commandDisabled || createAndDeleteDisabled); } void CSVWorld::RecordButtonBar::updatePrevNextButtons() { int rows = mTable.rowCount(); - + if (rows<=1) { mPrevButton->setDisabled (true); @@ -62,12 +61,12 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, mPrevButton->setIcon(QIcon(":/go-previous.png")); mPrevButton->setToolTip ("Switch to previous record"); buttonsLayout->addWidget (mPrevButton, 0); - + mNextButton = new QToolButton (this); mNextButton->setIcon(QIcon(":/go-next.png")); mNextButton->setToolTip ("Switch to next record"); buttonsLayout->addWidget (mNextButton, 1); - + buttonsLayout->addStretch(2); // optional buttons of the right section @@ -94,22 +93,22 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, mCloneButton->setIcon(QIcon(":/edit-clone.png")); mCloneButton->setToolTip ("Clone record"); buttonsLayout->addWidget(mCloneButton); - + mAddButton = new QToolButton (this); mAddButton->setIcon(QIcon(":/add.png")); mAddButton->setToolTip ("Add new record"); buttonsLayout->addWidget(mAddButton); - + mDeleteButton = new QToolButton (this); mDeleteButton->setIcon(QIcon(":/edit-delete.png")); mDeleteButton->setToolTip ("Delete record"); buttonsLayout->addWidget(mDeleteButton); - + mRevertButton = new QToolButton (this); mRevertButton->setIcon(QIcon(":/edit-undo.png")); mRevertButton->setToolTip ("Revert record"); buttonsLayout->addWidget(mRevertButton); - + setLayout (buttonsLayout); // connections @@ -132,7 +131,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); connect (&mTable, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); - + updateModificationButtons(); updatePrevNextButtons(); } @@ -170,7 +169,7 @@ void CSVWorld::RecordButtonBar::cloneRequest() } void CSVWorld::RecordButtonBar::nextId() -{ +{ int newRow = mTable.getModelIndex (mId.getId(), 0).row() + 1; if (newRow >= mTable.rowCount()) @@ -180,8 +179,8 @@ void CSVWorld::RecordButtonBar::nextId() newRow = 0; else return; - } - + } + emit switchToRow (newRow); } @@ -197,7 +196,7 @@ void CSVWorld::RecordButtonBar::prevId() else return; } - + emit switchToRow (newRow); } From ecbdd7d753d0fe5ef7fd9690849457410b61f019 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 14 Jul 2015 13:52:48 +0200 Subject: [PATCH 083/441] hooked up script subview buttons to bottom box (enables add and clone) --- apps/opencs/view/world/scriptsubview.cpp | 15 +++++++++------ apps/opencs/view/world/scriptsubview.hpp | 2 ++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index f65f77285..181400c88 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -23,7 +23,7 @@ void CSVWorld::ScriptSubView::addButtonBar() if (mButtons) return; - mButtons = new RecordButtonBar (getUniversalId(), *mModel, 0, &mCommandDispatcher, this); + mButtons = new RecordButtonBar (getUniversalId(), *mModel, mBottom, &mCommandDispatcher, this); mLayout.insertWidget (1, mButtons); @@ -61,16 +61,14 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: throw std::logic_error ("Can't find script column"); mEditor->setPlainText (mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString()); + // bottom box and buttons + mBottom = new TableBottomBox (CreatorFactory(), document, id, this); - // buttons if (CSMSettings::UserSettings::instance().setting ("script-editor/toolbar", QString("true")) == "true") addButtonBar(); - // bottom box - mBottom = new TableBottomBox (CreatorFactory(), document, id, this); - connect (mBottom, SIGNAL (requestFocus (const std::string&)), - this, SLOT (requestFocus (const std::string&))); + this, SLOT (switchToId (const std::string&))); mLayout.addWidget (mBottom); @@ -216,3 +214,8 @@ void CSVWorld::ScriptSubView::switchToRow (int row) std::vector selection (1, id); mCommandDispatcher.setSelection (selection); } + +void CSVWorld::ScriptSubView::switchToId (const std::string& id) +{ + switchToRow (mModel->getModelIndex (id, 0).row()); +} diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 09f7907ee..6e5276c68 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -69,6 +69,8 @@ namespace CSVWorld void updateStatusBar(); void switchToRow (int row); + + void switchToId (const std::string& id); }; } From c5eec822ae8e0cb2c28beae796467bcb298165b3 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 16 Jul 2015 12:36:20 +0200 Subject: [PATCH 084/441] display script errors in script subview --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/scripterrortable.cpp | 87 +++++++++++++++++++++ apps/opencs/view/world/scripterrortable.hpp | 45 +++++++++++ apps/opencs/view/world/scriptsubview.cpp | 33 +++++++- apps/opencs/view/world/scriptsubview.hpp | 4 + 5 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 apps/opencs/view/world/scripterrortable.cpp create mode 100644 apps/opencs/view/world/scripterrortable.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index ee1d5253d..70abe53fa 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -64,7 +64,7 @@ opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable - dialoguespinbox recordbuttonbar tableeditidaction + dialoguespinbox recordbuttonbar tableeditidaction scripterrortable ) opencs_units_noqt (view/world diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp new file mode 100644 index 000000000..0c97339dc --- /dev/null +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -0,0 +1,87 @@ + +#include "scripterrortable.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" + + +void CSVWorld::ScriptErrorTable::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) +{ + addMessage (message, type==Compiler::ErrorHandler::WarningMessage ? + CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error, loc.mLine); +} + +void CSVWorld::ScriptErrorTable::report (const std::string& message, Type type) +{ + addMessage (message, type==Compiler::ErrorHandler::WarningMessage ? + CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error); +} + +void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, + CSMDoc::Message::Severity severity, int line) +{ + int row = rowCount(); + + setRowCount (row+1); + + setItem (row, 0, new QTableWidgetItem ("")); + + if (line!=-1) + { + QTableWidgetItem *item = new QTableWidgetItem; + item->setData (Qt::DisplayRole, line+1); + setItem (row, 1, item); + } + + setItem (row, 2, new QTableWidgetItem (QString::fromUtf8 (message.c_str()))); +} + +CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent) +: QTableWidget (parent), mContext (document.getData()) +{ + setColumnCount (3); + + QStringList headers; + headers << "Severity" << "Line" << "Description"; + setHorizontalHeaderLabels (headers); + horizontalHeader()->setStretchLastSection (true); + + Compiler::registerExtensions (mExtensions); + mContext.setExtensions (&mExtensions); +} + +void CSVWorld::ScriptErrorTable::updateUserSetting (const QString& name, const QStringList& value) +{ + +} + +void CSVWorld::ScriptErrorTable::update (const std::string& source) +{ + setRowCount (0); + + try + { + std::istringstream input (source); + + Compiler::Scanner scanner (*this, input, mContext.getExtensions()); + + Compiler::FileParser parser (*this, mContext); + + scanner.scan (parser); + } + catch (const Compiler::SourceException&) + { + // error has already been reported via error handler + } + catch (const std::exception& error) + { + addMessage (error.what(), CSMDoc::Message::Severity_SeriousError); + } +} diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp new file mode 100644 index 000000000..791125c12 --- /dev/null +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -0,0 +1,45 @@ +#ifndef CSV_WORLD_SCRIPTERRORTABLE_H +#define CSV_WORLD_SCRIPTERRORTABLE_H + +#include + +#include +#include + +#include "../../model/world/scriptcontext.hpp" +#include "../../model/doc/messages.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSVWorld +{ + class ScriptErrorTable : public QTableWidget, private Compiler::ErrorHandler + { + Q_OBJECT + + Compiler::Extensions mExtensions; + CSMWorld::ScriptContext mContext; + + virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type); + ///< Report error to the user. + + virtual void report (const std::string& message, Type type); + ///< Report a file related error + + void addMessage (const std::string& message, CSMDoc::Message::Severity severity, + int line = -1); + + public: + + ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent = 0); + + void updateUserSetting (const QString& name, const QStringList& value); + + void update (const std::string& source); + }; +} + +#endif diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 181400c88..d48e647d9 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" @@ -17,6 +18,7 @@ #include "recordbuttonbar.hpp" #include "tablebottombox.hpp" #include "genericcreator.hpp" +#include "scripterrortable.hpp" void CSVWorld::ScriptSubView::addButtonBar() { @@ -40,7 +42,16 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: std::vector selection (1, id.getId()); mCommandDispatcher.setSelection (selection); - mLayout.addWidget (mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this), 2); + mMain = new QSplitter (this); + mMain->setOrientation (Qt::Vertical); + mLayout.addWidget (mMain, 2); + + mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this); + mMain->addWidget (mEditor); + mMain->setCollapsible (0, false); + + mErrors = new ScriptErrorTable (document, this); + mMain->addWidget (mErrors); QWidget *widget = new QWidget (this);; widget->setLayout (&mLayout); @@ -60,7 +71,9 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: if (mColumn==-1) throw std::logic_error ("Can't find script column"); - mEditor->setPlainText (mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString()); + QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString(); + + mEditor->setPlainText (source); // bottom box and buttons mBottom = new TableBottomBox (CreatorFactory(), document, id, this); @@ -83,6 +96,8 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: updateStatusBar(); connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar())); + + mErrors->update (source.toUtf8().constData()); } void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value) @@ -116,6 +131,8 @@ void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStr if (mButtons) mButtons->updateUserSetting (name, value); + + mErrors->updateUserSetting (name, value); } void CSVWorld::ScriptSubView::setStatusBar (bool show) @@ -173,8 +190,12 @@ void CSVWorld::ScriptSubView::textChanged() ScriptEdit::ChangeLock lock (*mEditor); + QString source = mEditor->toPlainText(); + mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel, - mModel->getModelIndex (getUniversalId().getId(), mColumn), mEditor->toPlainText())); + mModel->getModelIndex (getUniversalId().getId(), mColumn), source)); + + mErrors->update (source.toUtf8().constData()); } void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) @@ -189,9 +210,13 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo if (index.row()>=topLeft.row() && index.row()<=bottomRight.row() && index.column()>=topLeft.column() && index.column()<=bottomRight.column()) { + QString source = mModel->data (index).toString(); + QTextCursor cursor = mEditor->textCursor(); - mEditor->setPlainText (mModel->data (index).toString()); + mEditor->setPlainText (source); mEditor->setTextCursor (cursor); + + mErrors->update (source.toUtf8().constData()); } } diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 6e5276c68..0b6f4d923 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -10,6 +10,7 @@ class QModelIndex; class QLabel; class QVBoxLayout; +class QSplitter; namespace CSMDoc { @@ -26,6 +27,7 @@ namespace CSVWorld class ScriptEdit; class RecordButtonBar; class TableBottomBox; + class ScriptErrorTable; class ScriptSubView : public CSVDoc::SubView { @@ -39,6 +41,8 @@ namespace CSVWorld RecordButtonBar *mButtons; CSMWorld::CommandDispatcher mCommandDispatcher; QVBoxLayout mLayout; + QSplitter *mMain; + ScriptErrorTable *mErrors; private: From 3ea445cc67c767fca27d161bffe8b882eb8fe7e7 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 16 Jul 2015 14:09:14 +0200 Subject: [PATCH 085/441] improved error reporting --- apps/opencs/view/world/scripterrortable.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index 0c97339dc..cb793237a 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -14,7 +14,10 @@ void CSVWorld::ScriptErrorTable::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) { - addMessage (message, type==Compiler::ErrorHandler::WarningMessage ? + std::ostringstream stream; + stream << message << " (" << loc.mLiteral << ")"; + + addMessage (stream.str(), type==Compiler::ErrorHandler::WarningMessage ? CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error, loc.mLine); } From 092204bd8246d7858e7179daf093b97af8024cae Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 16 Jul 2015 14:46:02 +0200 Subject: [PATCH 086/441] refactored mapping from message severity enum to string --- apps/opencs/model/doc/messages.cpp | 16 +++++++++++++++- apps/opencs/model/doc/messages.hpp | 8 +++++--- apps/opencs/model/tools/reportmodel.cpp | 20 +++++++------------- apps/opencs/view/world/scripterrortable.cpp | 2 +- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/apps/opencs/model/doc/messages.cpp b/apps/opencs/model/doc/messages.cpp index bd6e808c8..c8d26d39b 100644 --- a/apps/opencs/model/doc/messages.cpp +++ b/apps/opencs/model/doc/messages.cpp @@ -8,6 +8,20 @@ CSMDoc::Message::Message (const CSMWorld::UniversalId& id, const std::string& me : mId (id), mMessage (message), mHint (hint), mSeverity (severity) {} +std::string CSMDoc::Message::toString (Severity severity) +{ + switch (severity) + { + case CSMDoc::Message::Severity_Info: return "Information"; + case CSMDoc::Message::Severity_Warning: return "Warning"; + case CSMDoc::Message::Severity_Error: return "Error"; + case CSMDoc::Message::Severity_SeriousError: return "Serious Error"; + case CSMDoc::Message::Severity_Default: break; + } + + return ""; +} + CSMDoc::Messages::Messages (Message::Severity default_) : mDefault (default_) @@ -18,7 +32,7 @@ void CSMDoc::Messages::add (const CSMWorld::UniversalId& id, const std::string& { if (severity==Message::Severity_Default) severity = mDefault; - + mMessages.push_back (Message (id, message, hint, severity)); } diff --git a/apps/opencs/model/doc/messages.hpp b/apps/opencs/model/doc/messages.hpp index 86f5feb15..429feae4e 100644 --- a/apps/opencs/model/doc/messages.hpp +++ b/apps/opencs/model/doc/messages.hpp @@ -21,18 +21,20 @@ namespace CSMDoc // reporting it correctly Severity_Default = 4 }; - + CSMWorld::UniversalId mId; std::string mMessage; std::string mHint; Severity mSeverity; Message(); - + Message (const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint, Severity severity); + + static std::string toString (Severity severity); }; - + class Messages { public: diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 480691710..4bf7d1581 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -13,7 +13,7 @@ CSMTools::ReportModel::ReportModel (bool fieldColumn, bool severityColumn) if (severityColumn) mColumnSeverity = index++; - + if (fieldColumn) mColumnField = index++; @@ -46,7 +46,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const case Column_Type: return static_cast (mRows.at (index.row()).mId.getType()); - + case Column_Id: { CSMWorld::UniversalId id = mRows.at (index.row()).mId; @@ -56,7 +56,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const return QString ("-"); } - + case Column_Hint: return QString::fromUtf8 (mRows.at (index.row()).mHint.c_str()); @@ -85,16 +85,10 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const if (index.column()==mColumnSeverity) { - switch (mRows.at (index.row()).mSeverity) - { - case CSMDoc::Message::Severity_Info: return "Information"; - case CSMDoc::Message::Severity_Warning: return "Warning"; - case CSMDoc::Message::Severity_Error: return "Error"; - case CSMDoc::Message::Severity_SeriousError: return "Serious Error"; - case CSMDoc::Message::Severity_Default: break; - } + return QString::fromUtf8 ( + CSMDoc::Message::toString (mRows.at (index.row()).mSeverity).c_str()); } - + return QVariant(); } @@ -144,7 +138,7 @@ bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& p void CSMTools::ReportModel::add (const CSMDoc::Message& message) { beginInsertRows (QModelIndex(), mRows.size(), mRows.size()); - + mRows.push_back (message); endInsertRows(); diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index cb793237a..dfa036824 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -34,7 +34,7 @@ void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, setRowCount (row+1); - setItem (row, 0, new QTableWidgetItem ("")); + setItem (row, 0, new QTableWidgetItem (QString::fromUtf8 (CSMDoc::Message::toString (severity).c_str()))); if (line!=-1) { From 446ac998d8215a24265bdcbae90c2b043bb0101c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 17 Jul 2015 11:52:59 +0200 Subject: [PATCH 087/441] consider script warning settings for script subview --- apps/opencs/view/world/scripterrortable.cpp | 19 +++++++++++++++++-- apps/opencs/view/world/scripterrortable.hpp | 2 ++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index dfa036824..29f1d6820 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -10,7 +10,7 @@ #include #include "../../model/doc/document.hpp" - +#include "../../model/settings/usersettings.hpp" void CSVWorld::ScriptErrorTable::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) { @@ -44,6 +44,18 @@ void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, } setItem (row, 2, new QTableWidgetItem (QString::fromUtf8 (message.c_str()))); + + +} + +void CSVWorld::ScriptErrorTable::setWarningsMode (const QString& value) +{ + if (value=="Ignore") + Compiler::ErrorHandler::setWarningsMode (0); + else if (value=="Normal") + Compiler::ErrorHandler::setWarningsMode (1); + else if (value=="Strict") + Compiler::ErrorHandler::setWarningsMode (2); } CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent) @@ -58,11 +70,14 @@ CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, Compiler::registerExtensions (mExtensions); mContext.setExtensions (&mExtensions); + + setWarningsMode (CSMSettings::UserSettings::instance().settingValue ("script-editor/warnings")); } void CSVWorld::ScriptErrorTable::updateUserSetting (const QString& name, const QStringList& value) { - + if (name=="script-editor/warnings" && !value.isEmpty()) + setWarningsMode (value.at (0)); } void CSVWorld::ScriptErrorTable::update (const std::string& source) diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp index 791125c12..99557fb0d 100644 --- a/apps/opencs/view/world/scripterrortable.hpp +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -32,6 +32,8 @@ namespace CSVWorld void addMessage (const std::string& message, CSMDoc::Message::Severity severity, int line = -1); + void setWarningsMode (const QString& value); + public: ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent = 0); From e86b8c96f537a80a083f6577965ec707c3793814 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 17 Jul 2015 12:45:08 +0200 Subject: [PATCH 088/441] improved error table layout --- apps/opencs/view/world/scripterrortable.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index 29f1d6820..dc28b8907 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -66,7 +66,10 @@ CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QStringList headers; headers << "Severity" << "Line" << "Description"; setHorizontalHeaderLabels (headers); + horizontalHeader()->setResizeMode (0, QHeaderView::ResizeToContents); + horizontalHeader()->setResizeMode (1, QHeaderView::ResizeToContents); horizontalHeader()->setStretchLastSection (true); + verticalHeader()->hide(); Compiler::registerExtensions (mExtensions); mContext.setExtensions (&mExtensions); From 6576ac34f62f312792252a64e4885758b1cbba42 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 17 Jul 2015 13:06:51 +0200 Subject: [PATCH 089/441] made error table read only --- apps/opencs/view/world/scripterrortable.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index dc28b8907..460bedefc 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -34,16 +34,22 @@ void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, setRowCount (row+1); - setItem (row, 0, new QTableWidgetItem (QString::fromUtf8 (CSMDoc::Message::toString (severity).c_str()))); + QTableWidgetItem *severityItem = new QTableWidgetItem ( + QString::fromUtf8 (CSMDoc::Message::toString (severity).c_str())); + severityItem->setFlags (severityItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 0, severityItem); if (line!=-1) { - QTableWidgetItem *item = new QTableWidgetItem; - item->setData (Qt::DisplayRole, line+1); - setItem (row, 1, item); + QTableWidgetItem *lineItem = new QTableWidgetItem; + lineItem->setData (Qt::DisplayRole, line+1); + lineItem->setFlags (lineItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 1, lineItem); } - setItem (row, 2, new QTableWidgetItem (QString::fromUtf8 (message.c_str()))); + QTableWidgetItem *messageItem = new QTableWidgetItem (QString::fromUtf8 (message.c_str())); + messageItem->setFlags (messageItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 2, messageItem); } From 757f7d895a191add818a0c41e593db92527a4762 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 17 Jul 2015 13:42:25 +0200 Subject: [PATCH 090/441] jump to line in source text when clicking on an error in error table --- apps/opencs/view/world/scripterrortable.cpp | 10 ++++++++++ apps/opencs/view/world/scripterrortable.hpp | 8 ++++++++ apps/opencs/view/world/scriptsubview.cpp | 16 ++++++++++++++++ apps/opencs/view/world/scriptsubview.hpp | 2 ++ 4 files changed, 36 insertions(+) diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index 460bedefc..242d6c8ac 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -77,10 +77,14 @@ CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, horizontalHeader()->setStretchLastSection (true); verticalHeader()->hide(); + setSelectionMode (QAbstractItemView::NoSelection); + Compiler::registerExtensions (mExtensions); mContext.setExtensions (&mExtensions); setWarningsMode (CSMSettings::UserSettings::instance().settingValue ("script-editor/warnings")); + + connect (this, SIGNAL (cellClicked (int, int)), this, SLOT (cellClicked (int, int))); } void CSVWorld::ScriptErrorTable::updateUserSetting (const QString& name, const QStringList& value) @@ -112,3 +116,9 @@ void CSVWorld::ScriptErrorTable::update (const std::string& source) addMessage (error.what(), CSMDoc::Message::Severity_SeriousError); } } + +void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) +{ + int line = item (row, 1)->data (Qt::DisplayRole).toInt(); + emit highlightError (line-1); +} diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp index 99557fb0d..5c6e75164 100644 --- a/apps/opencs/view/world/scripterrortable.hpp +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -41,6 +41,14 @@ namespace CSVWorld void updateUserSetting (const QString& name, const QStringList& value); void update (const std::string& source); + + private slots: + + void cellClicked (int row, int column); + + signals: + + void highlightError (int line); }; } diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index d48e647d9..36e21d53b 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -98,6 +98,8 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar())); mErrors->update (source.toUtf8().constData()); + + connect (mErrors, SIGNAL (highlightError (int)), this, SLOT (highlightError (int))); } void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value) @@ -244,3 +246,17 @@ void CSVWorld::ScriptSubView::switchToId (const std::string& id) { switchToRow (mModel->getModelIndex (id, 0).row()); } + +void CSVWorld::ScriptSubView::highlightError (int line) +{ + QTextCursor cursor = mEditor->textCursor(); + + cursor.movePosition (QTextCursor::Start); + if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) + { +// cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); + } + + mEditor->setFocus(); + mEditor->setTextCursor (cursor); +} diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 0b6f4d923..6be943bb8 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -75,6 +75,8 @@ namespace CSVWorld void switchToRow (int row); void switchToId (const std::string& id); + + void highlightError (int line); }; } From 527ab1aff751f2027385466f71cb67069c5dd4a4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 17 Jul 2015 13:53:22 +0200 Subject: [PATCH 091/441] consider column when jumping to error in source text --- apps/opencs/view/world/scripterrortable.cpp | 20 +++++++++++++------- apps/opencs/view/world/scripterrortable.hpp | 4 ++-- apps/opencs/view/world/scriptsubview.cpp | 9 ++++----- apps/opencs/view/world/scriptsubview.hpp | 2 +- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index 242d6c8ac..ec84a8328 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -18,7 +18,8 @@ void CSVWorld::ScriptErrorTable::report (const std::string& message, const Compi stream << message << " (" << loc.mLiteral << ")"; addMessage (stream.str(), type==Compiler::ErrorHandler::WarningMessage ? - CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error, loc.mLine); + CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error, + loc.mLine, loc.mColumn-loc.mLiteral.length()); } void CSVWorld::ScriptErrorTable::report (const std::string& message, Type type) @@ -28,7 +29,7 @@ void CSVWorld::ScriptErrorTable::report (const std::string& message, Type type) } void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, - CSMDoc::Message::Severity severity, int line) + CSMDoc::Message::Severity severity, int line, int column) { int row = rowCount(); @@ -45,13 +46,16 @@ void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, lineItem->setData (Qt::DisplayRole, line+1); lineItem->setFlags (lineItem->flags() ^ Qt::ItemIsEditable); setItem (row, 1, lineItem); + + QTableWidgetItem *columnItem = new QTableWidgetItem; + columnItem->setData (Qt::DisplayRole, column); + columnItem->setFlags (columnItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 3, columnItem); } QTableWidgetItem *messageItem = new QTableWidgetItem (QString::fromUtf8 (message.c_str())); messageItem->setFlags (messageItem->flags() ^ Qt::ItemIsEditable); setItem (row, 2, messageItem); - - } void CSVWorld::ScriptErrorTable::setWarningsMode (const QString& value) @@ -67,7 +71,7 @@ void CSVWorld::ScriptErrorTable::setWarningsMode (const QString& value) CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent) : QTableWidget (parent), mContext (document.getData()) { - setColumnCount (3); + setColumnCount (4); QStringList headers; headers << "Severity" << "Line" << "Description"; @@ -76,6 +80,7 @@ CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, horizontalHeader()->setResizeMode (1, QHeaderView::ResizeToContents); horizontalHeader()->setStretchLastSection (true); verticalHeader()->hide(); + setColumnHidden (3, true); setSelectionMode (QAbstractItemView::NoSelection); @@ -119,6 +124,7 @@ void CSVWorld::ScriptErrorTable::update (const std::string& source) void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) { - int line = item (row, 1)->data (Qt::DisplayRole).toInt(); - emit highlightError (line-1); + int scriptLine = item (row, 1)->data (Qt::DisplayRole).toInt(); + int scriptColumn = item (row, 3)->data (Qt::DisplayRole).toInt(); + emit highlightError (scriptLine-1, scriptColumn); } diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp index 5c6e75164..f16a96a74 100644 --- a/apps/opencs/view/world/scripterrortable.hpp +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -30,7 +30,7 @@ namespace CSVWorld ///< Report a file related error void addMessage (const std::string& message, CSMDoc::Message::Severity severity, - int line = -1); + int line = -1, int column = -1); void setWarningsMode (const QString& value); @@ -48,7 +48,7 @@ namespace CSVWorld signals: - void highlightError (int line); + void highlightError (int line, int column); }; } diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 36e21d53b..7b5b1131e 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -99,7 +99,8 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: mErrors->update (source.toUtf8().constData()); - connect (mErrors, SIGNAL (highlightError (int)), this, SLOT (highlightError (int))); + connect (mErrors, SIGNAL (highlightError (int, int)), + this, SLOT (highlightError (int, int))); } void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value) @@ -247,15 +248,13 @@ void CSVWorld::ScriptSubView::switchToId (const std::string& id) switchToRow (mModel->getModelIndex (id, 0).row()); } -void CSVWorld::ScriptSubView::highlightError (int line) +void CSVWorld::ScriptSubView::highlightError (int line, int column) { QTextCursor cursor = mEditor->textCursor(); cursor.movePosition (QTextCursor::Start); if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) - { -// cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); - } + cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); mEditor->setFocus(); mEditor->setTextCursor (cursor); diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 6be943bb8..a9c8dccb3 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -76,7 +76,7 @@ namespace CSVWorld void switchToId (const std::string& id); - void highlightError (int line); + void highlightError (int line, int column); }; } From 9abc57d988b7027be6460ca6ea1964954be5c3a3 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 17 Jul 2015 13:59:50 +0200 Subject: [PATCH 092/441] do not try to jump to source location for errors that do not have a source location --- apps/opencs/view/world/scripterrortable.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index ec84a8328..20b197c54 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -124,7 +124,10 @@ void CSVWorld::ScriptErrorTable::update (const std::string& source) void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) { - int scriptLine = item (row, 1)->data (Qt::DisplayRole).toInt(); - int scriptColumn = item (row, 3)->data (Qt::DisplayRole).toInt(); - emit highlightError (scriptLine-1, scriptColumn); + if (item (row, 1)) + { + int scriptLine = item (row, 1)->data (Qt::DisplayRole).toInt(); + int scriptColumn = item (row, 3)->data (Qt::DisplayRole).toInt(); + emit highlightError (scriptLine-1, scriptColumn); + } } From 7f89d3688fca4139beed14cf4a22c45777d62f0d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 18 Jul 2015 13:09:17 +0200 Subject: [PATCH 093/441] put script compilation in script subview behind a timer --- apps/opencs/view/world/scriptsubview.cpp | 29 ++++++++++++++++++++++-- apps/opencs/view/world/scriptsubview.hpp | 6 +++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 7b5b1131e..dfdca8b48 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" @@ -35,6 +36,12 @@ void CSVWorld::ScriptSubView::addButtonBar() mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); } +void CSVWorld::ScriptSubView::recompile() +{ + if (!mCompileDelay->isActive()) + mCompileDelay->start (5000); +} + CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) @@ -101,6 +108,12 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: connect (mErrors, SIGNAL (highlightError (int, int)), this, SLOT (highlightError (int, int))); + + mCompileDelay = new QTimer (this); + mCompileDelay->setSingleShot (true); + connect (mCompileDelay, SIGNAL (timeout()), this, SLOT (updateRequest())); + + recompile(); } void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value) @@ -136,6 +149,9 @@ void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStr mButtons->updateUserSetting (name, value); mErrors->updateUserSetting (name, value); + + if (name=="script-editor/warnings") + recompile(); } void CSVWorld::ScriptSubView::setStatusBar (bool show) @@ -198,7 +214,7 @@ void CSVWorld::ScriptSubView::textChanged() mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel, mModel->getModelIndex (getUniversalId().getId(), mColumn), source)); - mErrors->update (source.toUtf8().constData()); + recompile(); } void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) @@ -219,7 +235,7 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo mEditor->setPlainText (source); mEditor->setTextCursor (cursor); - mErrors->update (source.toUtf8().constData()); + recompile(); } } @@ -259,3 +275,12 @@ void CSVWorld::ScriptSubView::highlightError (int line, int column) mEditor->setFocus(); mEditor->setTextCursor (cursor); } + +void CSVWorld::ScriptSubView::updateRequest() +{ + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); + + QString source = mModel->data (index).toString(); + + mErrors->update (source.toUtf8().constData()); +} diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index a9c8dccb3..3481989f1 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -11,6 +11,7 @@ class QModelIndex; class QLabel; class QVBoxLayout; class QSplitter; +class QTime; namespace CSMDoc { @@ -43,11 +44,14 @@ namespace CSVWorld QVBoxLayout mLayout; QSplitter *mMain; ScriptErrorTable *mErrors; + QTimer *mCompileDelay; private: void addButtonBar(); + void recompile(); + public: ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); @@ -77,6 +81,8 @@ namespace CSVWorld void switchToId (const std::string& id); void highlightError (int line, int column); + + void updateRequest(); }; } From 25bb2983e2195819bf37495a4d558b94c9a66631 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 18 Jul 2015 15:32:38 +0200 Subject: [PATCH 094/441] make compilation delay configurable via a user setting --- apps/opencs/model/settings/usersettings.cpp | 6 ++++++ apps/opencs/view/world/scriptsubview.cpp | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index aa6730357..30ebdce7f 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -346,6 +346,12 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar"); toolbar->setDefaultValue ("true"); + Setting *delay = createSetting (Type_SpinBox, "compile-delay", + "Delay between updating of source errors"); + delay->setDefaultValue (100); + delay->setRange (0, 10000); + delay->setToolTip ("Delay in milliseconds"); + Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int"); formatInt->setDefaultValues (QStringList() << "Dark magenta"); formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index dfdca8b48..ee0adb6f5 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -39,7 +39,8 @@ void CSVWorld::ScriptSubView::addButtonBar() void CSVWorld::ScriptSubView::recompile() { if (!mCompileDelay->isActive()) - mCompileDelay->start (5000); + mCompileDelay->start ( + CSMSettings::UserSettings::instance().setting ("script-editor/compile-delay").toInt()); } CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) @@ -144,6 +145,10 @@ void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStr } } } + else if (name=="script-editor/compile-delay") + { + mCompileDelay->setInterval (value.at (0).toInt()); + } if (mButtons) mButtons->updateUserSetting (name, value); From 833844f5a48e8c117dad57ddf6a6521594bc5a1b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 18 Jul 2015 17:00:00 +0200 Subject: [PATCH 095/441] do not allow editing of deleted scripts --- apps/opencs/view/world/scripterrortable.cpp | 7 ++- apps/opencs/view/world/scripterrortable.hpp | 2 + apps/opencs/view/world/scriptsubview.cpp | 57 ++++++++++++++------- apps/opencs/view/world/scriptsubview.hpp | 5 ++ 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index 20b197c54..3e80c5bd4 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -100,7 +100,7 @@ void CSVWorld::ScriptErrorTable::updateUserSetting (const QString& name, const Q void CSVWorld::ScriptErrorTable::update (const std::string& source) { - setRowCount (0); + clear(); try { @@ -122,6 +122,11 @@ void CSVWorld::ScriptErrorTable::update (const std::string& source) } } +void CSVWorld::ScriptErrorTable::clear() +{ + setRowCount (0); +} + void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) { if (item (row, 1)) diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp index f16a96a74..98db425cf 100644 --- a/apps/opencs/view/world/scripterrortable.hpp +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -42,6 +42,8 @@ namespace CSVWorld void update (const std::string& source); + void clear(); + private slots: void cellClicked (int row, int column); diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index ee0adb6f5..d405d1765 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -38,11 +38,31 @@ void CSVWorld::ScriptSubView::addButtonBar() void CSVWorld::ScriptSubView::recompile() { - if (!mCompileDelay->isActive()) + if (!mCompileDelay->isActive() && !isDeleted()) mCompileDelay->start ( CSMSettings::UserSettings::instance().setting ("script-editor/compile-delay").toInt()); } +bool CSVWorld::ScriptSubView::isDeleted() const +{ + return mModel->data (mModel->getModelIndex (getUniversalId().getId(), mStateColumn)).toInt() + ==CSMWorld::RecordBase::State_Deleted; +} + +void CSVWorld::ScriptSubView::updateDeletedState() +{ + if (isDeleted()) + { + mErrors->clear(); + mEditor->setEnabled (false); + } + else + { + mEditor->setEnabled (true); + recompile(); + } +} + CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) @@ -68,16 +88,8 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: mModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); - for (int i=0; icolumnCount(); ++i) - if (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display)== - CSMWorld::ColumnBase::Display_ScriptFile) - { - mColumn = i; - break; - } - - if (mColumn==-1) - throw std::logic_error ("Can't find script column"); + mColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_ScriptText); + mStateColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString(); @@ -114,7 +126,7 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: mCompileDelay->setSingleShot (true); connect (mCompileDelay, SIGNAL (timeout()), this, SLOT (updateRequest())); - recompile(); + updateDeletedState(); } void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value) @@ -231,16 +243,21 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); - if (index.row()>=topLeft.row() && index.row()<=bottomRight.row() && - index.column()>=topLeft.column() && index.column()<=bottomRight.column()) + if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) { - QString source = mModel->data (index).toString(); + if (mStateColumn>=topLeft.column() && mStateColumn<=bottomRight.column()) + updateDeletedState(); - QTextCursor cursor = mEditor->textCursor(); - mEditor->setPlainText (source); - mEditor->setTextCursor (cursor); + if (mColumn>=topLeft.column() && mColumn<=bottomRight.column()) + { + QString source = mModel->data (index).toString(); - recompile(); + QTextCursor cursor = mEditor->textCursor(); + mEditor->setPlainText (source); + mEditor->setTextCursor (cursor); + + recompile(); + } } } @@ -262,6 +279,8 @@ void CSVWorld::ScriptSubView::switchToRow (int row) std::vector selection (1, id); mCommandDispatcher.setSelection (selection); + + updateDeletedState(); } void CSVWorld::ScriptSubView::switchToId (const std::string& id) diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 3481989f1..6125dd259 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -38,6 +38,7 @@ namespace CSVWorld CSMDoc::Document& mDocument; CSMWorld::IdTable *mModel; int mColumn; + int mStateColumn; TableBottomBox *mBottom; RecordButtonBar *mButtons; CSMWorld::CommandDispatcher mCommandDispatcher; @@ -52,6 +53,10 @@ namespace CSVWorld void recompile(); + bool isDeleted() const; + + void updateDeletedState(); + public: ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); From 1d722fcdbc61d1dc3cdf2189af7279e0d89f59c1 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 18 Jul 2015 17:47:50 +0200 Subject: [PATCH 096/441] incorrect sBribe GMSTs for new omwgame files (Fixes #2785) --- apps/opencs/model/doc/document.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index ce74fbeb9..b4d873245 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -799,9 +799,9 @@ void CSMDoc::Document::addGmsts() "sBookSkillMessage", "sBounty", "sBreath", - "sBribe", - "sBribe", - "sBribe", + "sBribe 10 Gold", + "sBribe 100 Gold", + "sBribe 1000 Gold", "sBribeFail", "sBribeSuccess", "sBuy", From 386f545c6709351928077a69482820f8452659f8 Mon Sep 17 00:00:00 2001 From: Koncord Date: Sun, 19 Jul 2015 23:37:20 +0900 Subject: [PATCH 097/441] Fix definition conflict --- components/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 2938dcd81..b2dbd3ff6 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -194,7 +194,10 @@ if (GIT_CHECKOUT) endif (GIT_CHECKOUT) if (WIN32) -target_link_libraries(components shlwapi) + target_link_libraries(components shlwapi) + if(MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNOGDI") + endif(MINGW) endif() # Fix for not visible pthreads functions for linker with glibc 2.15 From 73837e2e10c404a023366690e06e3be512845ba0 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 22 Jul 2015 08:36:06 +0200 Subject: [PATCH 098/441] updated credits file --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index ce76c104e..8ad420baf 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -59,6 +59,7 @@ Programmers Julien Voisin (jvoisin/ap0) Karl-Felix Glatzer (k1ll) Kevin Poitra (PuppyKevin) + Koncord Lars Söderberg (Lazaroth) lazydev Leon Saunders (emoose) From d037844d6ed0bf79f5084f0aeb7568e47303e90a Mon Sep 17 00:00:00 2001 From: Rohit Nirmal Date: Thu, 23 Jul 2015 22:22:22 -0500 Subject: [PATCH 099/441] Fix building OpenCS with Qt 5. --- apps/opencs/view/world/scripterrortable.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index 3e80c5bd4..415e6c9dc 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -76,8 +76,13 @@ CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QStringList headers; headers << "Severity" << "Line" << "Description"; setHorizontalHeaderLabels (headers); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + horizontalHeader()->setSectionResizeMode (0, QHeaderView::ResizeToContents); + horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents); +#else horizontalHeader()->setResizeMode (0, QHeaderView::ResizeToContents); horizontalHeader()->setResizeMode (1, QHeaderView::ResizeToContents); +#endif horizontalHeader()->setStretchLastSection (true); verticalHeader()->hide(); setColumnHidden (3, true); From ca67e4ea3aafd73fc4ff1b7b03b707d042cd074d Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 23 Jul 2015 20:35:16 +0300 Subject: [PATCH 100/441] Proper sorting of columns with enum values --- apps/opencs/model/world/idtableproxymodel.cpp | 14 ++++++++++++++ apps/opencs/model/world/idtableproxymodel.hpp | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 516644713..1f8f2e4b4 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -60,6 +60,20 @@ void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr(left.data(ColumnBase::Role_ColumnId).toInt()); + EnumColumnCache::const_iterator valuesIt = mEnumColumnCache.find(id); + if (valuesIt == mEnumColumnCache.end()) + { + if (Columns::hasEnums(id)) + { + valuesIt = mEnumColumnCache.insert(std::make_pair(id, Columns::getEnums(id))).first; + } + } + + if (valuesIt != mEnumColumnCache.end()) + { + return valuesIt->second[left.data().toInt()] < valuesIt->second[right.data().toInt()]; + } return QSortFilterProxyModel::lessThan(left, right); } diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index d2a240529..decad157c 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -11,6 +11,8 @@ #include "../filter/node.hpp" +#include "columns.hpp" + namespace CSMWorld { class IdTableProxyModel : public QSortFilterProxyModel @@ -20,6 +22,11 @@ namespace CSMWorld boost::shared_ptr mFilter; std::map mColumnMap; // column ID, column index in this model (or -1) + // Cache of enum values for enum columns (e.g. Modified, Record Type). + // Used to speed up comparisons during the sort by such columns. + typedef std::map > EnumColumnCache; + mutable EnumColumnCache mEnumColumnCache; + private: void updateColumnMap(); From 1041c3babbca303b05d32b553db0dd903b69d319 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 23 Jul 2015 21:05:00 +0300 Subject: [PATCH 101/441] IdTableProxyModel refreshes the filter when the source model data or rows changed --- apps/opencs/model/world/idtableproxymodel.cpp | 28 +++++++++++++++++++ apps/opencs/model/world/idtableproxymodel.hpp | 8 ++++++ apps/opencs/view/world/table.cpp | 4 --- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 1f8f2e4b4..ce9d44ed6 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -50,6 +50,24 @@ QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, i return mapFromSource (dynamic_cast (*sourceModel()).getModelIndex (id, column)); } +void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel *model) +{ + QSortFilterProxyModel::setSourceModel(model); + + connect(sourceModel(), + SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, + SLOT(sourceRowsChanged(const QModelIndex &, int, int))); + connect(sourceModel(), + SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, + SLOT(sourceRowsChanged(const QModelIndex &, int, int))); + connect(sourceModel(), + SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), + this, + SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &))); +} + void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr& filter) { beginResetModel(); @@ -82,3 +100,13 @@ void CSMWorld::IdTableProxyModel::refreshFilter() updateColumnMap(); invalidateFilter(); } + +void CSMWorld::IdTableProxyModel::sourceRowsChanged(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) +{ + refreshFilter(); +} + +void CSMWorld::IdTableProxyModel::sourceDataChanged(const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/) +{ + refreshFilter(); +} diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index decad157c..17c30361a 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -37,6 +37,8 @@ namespace CSMWorld virtual QModelIndex getModelIndex (const std::string& id, int column) const; + virtual void setSourceModel(QAbstractItemModel *model); + void setFilter (const boost::shared_ptr& filter); void refreshFilter(); @@ -46,6 +48,12 @@ namespace CSMWorld bool lessThan(const QModelIndex &left, const QModelIndex &right) const; virtual bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const; + + private slots: + + void sourceRowsChanged(const QModelIndex &parent, int start, int end); + + void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); }; } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 8146bc427..3c5b29be1 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -674,10 +674,6 @@ void CSVWorld::Table::tableSizeUpdate() } emit tableSizeChanged (size, deleted, modified); - - // not really related to tableSizeUpdate() but placed here for convenience rather than - // creating a bunch of extra connections & slot - mProxyModel->refreshFilter(); } void CSVWorld::Table::selectionSizeUpdate() From 77d5476d463c9b4ee35524ecaf94bf687deba441 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 25 Jul 2015 17:57:40 +0300 Subject: [PATCH 102/441] Proper index creation for nested data --- apps/opencs/model/world/idtree.cpp | 4 ++-- apps/opencs/model/world/nestedtableproxymodel.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 545ba383d..d8e8c6107 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -171,10 +171,10 @@ QModelIndex CSMWorld::IdTree::index (int row, int column, const QModelIndex& par encodedId = this->foldIndexAddress(parent); } - if (row<0 || row>=idCollection()->getSize()) + if (row < 0 || row >= rowCount(parent)) return QModelIndex(); - if (column<0 || column>=idCollection()->getColumns()) + if (column < 0 || column >= columnCount(parent)) return QModelIndex(); return createIndex(row, column, encodedId); // store internal id diff --git a/apps/opencs/model/world/nestedtableproxymodel.cpp b/apps/opencs/model/world/nestedtableproxymodel.cpp index 052e629aa..edcc7a070 100644 --- a/apps/opencs/model/world/nestedtableproxymodel.cpp +++ b/apps/opencs/model/world/nestedtableproxymodel.cpp @@ -75,10 +75,10 @@ QModelIndex CSMWorld::NestedTableProxyModel::index(int row, int column, const QM { assert (!parent.isValid()); - int rows = mMainModel->rowCount(parent); - int columns = mMainModel->columnCount(parent); + int numRows = rowCount(parent); + int numColumns = columnCount(parent); - if (row < 0 || row >= rows || column < 0 || column >= columns) + if (row < 0 || row >= numRows || column < 0 || column >= numColumns) return QModelIndex(); return createIndex(row, column); From 6c8862e542730f1b8a21757126381ad90c950d23 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 3 Jul 2015 02:42:40 +0200 Subject: [PATCH 103/441] Fix a typo --- apps/opencs/model/world/columns.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 6871d7c95..329d8a7a5 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -92,7 +92,7 @@ namespace CSMWorld { ColumnId_Trainer, "Trainer" }, { ColumnId_Spellmaking, "Spellmaking" }, { ColumnId_EnchantingService, "Enchanting Service" }, - { ColumnId_RepairService, "Repair Serivce" }, + { ColumnId_RepairService, "Repair Service" }, { ColumnId_ApparatusType, "Apparatus Type" }, { ColumnId_ArmorType, "Armor Type" }, { ColumnId_Health, "Health" }, From f24b293c1332259c0ecc672b2ea9815eca417283 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 26 Jul 2015 20:06:30 +0300 Subject: [PATCH 104/441] Create a custom signal to inform about a row addition in IdTableProxyModel --- apps/opencs/model/world/idtableproxymodel.cpp | 53 ++++++++++++------- apps/opencs/model/world/idtableproxymodel.hpp | 20 +++++-- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index ce9d44ed6..8beccd497 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -7,23 +7,23 @@ void CSMWorld::IdTableProxyModel::updateColumnMap() { - mColumnMap.clear(); + Q_ASSERT(mSourceModel != NULL); + mColumnMap.clear(); if (mFilter) { std::vector columns = mFilter->getReferencedColumns(); - - const IdTableBase& table = dynamic_cast (*sourceModel()); - for (std::vector::const_iterator iter (columns.begin()); iter!=columns.end(); ++iter) - mColumnMap.insert (std::make_pair (*iter, - table.searchColumnIndex (static_cast (*iter)))); + mColumnMap.insert (std::make_pair (*iter, + mSourceModel->searchColumnIndex (static_cast (*iter)))); } } bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const { + Q_ASSERT(mSourceModel != NULL); + // It is not possible to use filterAcceptsColumn() and check for // sourceModel()->headerData (sourceColumn, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags) // because the sourceColumn parameter excludes the hidden columns, i.e. wrong columns can @@ -35,34 +35,37 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelI if (!mFilter) return true; - return mFilter->test ( - dynamic_cast (*sourceModel()), sourceRow, mColumnMap); + return mFilter->test (*mSourceModel, sourceRow, mColumnMap); } CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) -: QSortFilterProxyModel (parent) + : QSortFilterProxyModel (parent), + mSourceModel(NULL) { setSortCaseSensitivity (Qt::CaseInsensitive); } QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const { - return mapFromSource (dynamic_cast (*sourceModel()).getModelIndex (id, column)); + Q_ASSERT(mSourceModel != NULL); + + return mapFromSource(mSourceModel->getModelIndex (id, column)); } void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel *model) { QSortFilterProxyModel::setSourceModel(model); - connect(sourceModel(), - SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, - SLOT(sourceRowsChanged(const QModelIndex &, int, int))); - connect(sourceModel(), + mSourceModel = dynamic_cast(sourceModel()); + connect(mSourceModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, - SLOT(sourceRowsChanged(const QModelIndex &, int, int))); - connect(sourceModel(), + SLOT(sourceRowsInserted(const QModelIndex &, int, int))); + connect(mSourceModel, + SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, + SLOT(sourceRowsRemoved(const QModelIndex &, int, int))); + connect(mSourceModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &))); @@ -95,13 +98,27 @@ bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex &left, const QModel return QSortFilterProxyModel::lessThan(left, right); } +QString CSMWorld::IdTableProxyModel::getRecordId(int sourceRow) const +{ + Q_ASSERT(mSourceModel != NULL); + + int idColumn = mSourceModel->findColumnIndex(Columns::ColumnId_Id); + return mSourceModel->data(mSourceModel->index(sourceRow, idColumn)).toString(); +} + void CSMWorld::IdTableProxyModel::refreshFilter() { updateColumnMap(); invalidateFilter(); } -void CSMWorld::IdTableProxyModel::sourceRowsChanged(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) +void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &/*parent*/, int /*start*/, int end) +{ + refreshFilter(); + emit rowAdded(getRecordId(end).toUtf8().constData()); +} + +void CSMWorld::IdTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) { refreshFilter(); } diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index 17c30361a..cf31b5c11 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -27,6 +27,10 @@ namespace CSMWorld typedef std::map > EnumColumnCache; mutable EnumColumnCache mEnumColumnCache; + protected: + + IdTableBase *mSourceModel; + private: void updateColumnMap(); @@ -45,15 +49,23 @@ namespace CSMWorld protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; virtual bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const; - private slots: + QString getRecordId(int sourceRow) const; + + protected slots: + + virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end); + + virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end); + + virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - void sourceRowsChanged(const QModelIndex &parent, int start, int end); + signals: - void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void rowAdded(const std::string &id); }; } From 9461baa2eaa242579545d79d9da9702acf0bff6d Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 26 Jul 2015 20:07:36 +0300 Subject: [PATCH 105/441] Inform about a row addition after re-sorting in InfoTableProxyModel --- .../model/world/infotableproxymodel.cpp | 56 ++++++++++++++----- .../model/world/infotableproxymodel.hpp | 12 ++-- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/apps/opencs/model/world/infotableproxymodel.cpp b/apps/opencs/model/world/infotableproxymodel.cpp index 6216291d0..c6216ba5d 100644 --- a/apps/opencs/model/world/infotableproxymodel.cpp +++ b/apps/opencs/model/world/infotableproxymodel.cpp @@ -9,16 +9,17 @@ namespace { QString toLower(const QString &str) { - return QString::fromUtf8(Misc::StringUtils::lowerCase(str.toStdString()).c_str()); + return QString::fromUtf8(Misc::StringUtils::lowerCase(str.toUtf8().constData()).c_str()); } } CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type type, QObject *parent) : IdTableProxyModel(parent), mType(type), - mSourceModel(NULL), mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic : - Columns::ColumnId_Journal) + Columns::ColumnId_Journal), + mInfoColumnIndex(-1), + mLastAddedSourceRow(-1) { Q_ASSERT(type == UniversalId::Type_TopicInfos || type == UniversalId::Type_JournalInfos); } @@ -26,23 +27,18 @@ CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type t void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { IdTableProxyModel::setSourceModel(sourceModel); - mSourceModel = dynamic_cast(sourceModel); + if (mSourceModel != NULL) { - connect(mSourceModel, - SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, - SLOT(modelRowsChanged(const QModelIndex &, int, int))); - connect(mSourceModel, - SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, - SLOT(modelRowsChanged(const QModelIndex &, int, int))); + mInfoColumnIndex = mSourceModel->findColumnIndex(mInfoColumnId); mFirstRowCache.clear(); } } bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { + Q_ASSERT(mSourceModel != NULL); + QModelIndex first = mSourceModel->index(getFirstInfoRow(left.row()), left.column()); QModelIndex second = mSourceModel->index(getFirstInfoRow(right.row()), right.column()); @@ -56,8 +52,10 @@ bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QMod int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const { + Q_ASSERT(mSourceModel != NULL); + int row = currentRow; - int column = mSourceModel->findColumnIndex(mInfoColumnId); + int column = mInfoColumnIndex; QString info = toLower(mSourceModel->data(mSourceModel->index(row, column)).toString()); if (mFirstRowCache.contains(info)) @@ -73,7 +71,37 @@ int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const return row; } -void CSMWorld::InfoTableProxyModel::modelRowsChanged(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) +void CSMWorld::InfoTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) +{ + refreshFilter(); + mFirstRowCache.clear(); +} + +void CSMWorld::InfoTableProxyModel::sourceRowsInserted(const QModelIndex &/*parent*/, int /*start*/, int end) { + refreshFilter(); mFirstRowCache.clear(); + // We can't re-sort the model here, because the topic of the added row isn't set yet. + // Store the row index for using in the first dataChanged() after this row insertion. + mLastAddedSourceRow = end; +} + +void CSMWorld::InfoTableProxyModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + refreshFilter(); + + if (mLastAddedSourceRow != -1 && + topLeft.row() <= mLastAddedSourceRow && bottomRight.row() >= mLastAddedSourceRow) + { + // Now the topic of the last added row is set, + // so we can re-sort the model to ensure the corrent position of this row + int column = sortColumn(); + Qt::SortOrder order = sortOrder(); + sort(mInfoColumnIndex); // Restore the correct position of an added row + sort(column, order); // Restore the original sort order + emit rowAdded(getRecordId(mLastAddedSourceRow).toUtf8().constData()); + + // Make sure that we perform a re-sorting only in the first dataChanged() after a row insertion + mLastAddedSourceRow = -1; + } } diff --git a/apps/opencs/model/world/infotableproxymodel.hpp b/apps/opencs/model/world/infotableproxymodel.hpp index 28d6017b3..51d93f9a1 100644 --- a/apps/opencs/model/world/infotableproxymodel.hpp +++ b/apps/opencs/model/world/infotableproxymodel.hpp @@ -16,25 +16,29 @@ namespace CSMWorld Q_OBJECT UniversalId::Type mType; - IdTableBase *mSourceModel; Columns::ColumnId mInfoColumnId; ///< Contains ID for Topic or Journal ID + int mInfoColumnIndex; + int mLastAddedSourceRow; mutable QHash mFirstRowCache; int getFirstInfoRow(int currentRow) const; ///< Finds the first row with the same topic (journal entry) as in \a currentRow + ///< \a currentRow is a row of the source model. public: InfoTableProxyModel(UniversalId::Type type, QObject *parent = 0); - void setSourceModel(QAbstractItemModel *sourceModel); + virtual void setSourceModel(QAbstractItemModel *sourceModel); protected: virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - private slots: - void modelRowsChanged(const QModelIndex &parent, int start, int end); + protected slots: + virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end); + virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end); + virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); }; } From 6a82ce0d50163688e5f2053029f9a29b26d20281 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 26 Jul 2015 20:09:31 +0300 Subject: [PATCH 106/441] Rework Table to use rowAdded() signal of a proxy model Conflicts: apps/opencs/view/world/table.cpp apps/opencs/view/world/table.hpp --- apps/opencs/view/world/table.cpp | 11 +++++++---- apps/opencs/view/world/table.hpp | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 3c5b29be1..3d86323e1 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -374,8 +374,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mProxyModel, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); - connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); + //connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + // this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); + connect (mProxyModel, SIGNAL (rowAdded (const std::string &)), + this, SLOT (rowAdded (const std::string &))); /// \note This signal could instead be connected to a slot that filters out changes not affecting /// the records status column (for permanence reasons) @@ -793,12 +795,13 @@ void CSVWorld::Table::globalFilterModifiedChanged(int state) recordFilterChanged(mFilter); } -void CSVWorld::Table::rowsInsertedEvent(const QModelIndex& parent, int start, int end) +void CSVWorld::Table::rowAdded(const std::string &id) { tableSizeUpdate(); if(mJumpToAddedRecord) { - selectRow(end); + int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + selectRow(mProxyModel->getModelIndex(id, idColumn).row()); if(mUnselectAfterJump) clearSelection(); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 530b190e5..1c25832bd 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -148,7 +148,7 @@ namespace CSVWorld void globalFilterModifiedChanged (int state); - void rowsInsertedEvent(const QModelIndex& parent, int start, int end); + void rowAdded(const std::string &id); }; } From 3280aade39f8b6b2a88702246247d97d393dc2ac Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 26 Jul 2015 20:25:46 +0300 Subject: [PATCH 107/441] Don't inform about a nested row addition in proxy models for top-level tables --- apps/opencs/model/world/idtableproxymodel.cpp | 7 +++++-- apps/opencs/model/world/infotableproxymodel.cpp | 14 +++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 8beccd497..10fd92b46 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -112,10 +112,13 @@ void CSMWorld::IdTableProxyModel::refreshFilter() invalidateFilter(); } -void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &/*parent*/, int /*start*/, int end) +void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) { refreshFilter(); - emit rowAdded(getRecordId(end).toUtf8().constData()); + if (!parent.isValid()) + { + emit rowAdded(getRecordId(end).toUtf8().constData()); + } } void CSMWorld::IdTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) diff --git a/apps/opencs/model/world/infotableproxymodel.cpp b/apps/opencs/model/world/infotableproxymodel.cpp index c6216ba5d..f55f775ab 100644 --- a/apps/opencs/model/world/infotableproxymodel.cpp +++ b/apps/opencs/model/world/infotableproxymodel.cpp @@ -77,13 +77,17 @@ void CSMWorld::InfoTableProxyModel::sourceRowsRemoved(const QModelIndex &/*paren mFirstRowCache.clear(); } -void CSMWorld::InfoTableProxyModel::sourceRowsInserted(const QModelIndex &/*parent*/, int /*start*/, int end) +void CSMWorld::InfoTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) { refreshFilter(); - mFirstRowCache.clear(); - // We can't re-sort the model here, because the topic of the added row isn't set yet. - // Store the row index for using in the first dataChanged() after this row insertion. - mLastAddedSourceRow = end; + + if (!parent.isValid()) + { + mFirstRowCache.clear(); + // We can't re-sort the model here, because the topic of the added row isn't set yet. + // Store the row index for using in the first dataChanged() after this row insertion. + mLastAddedSourceRow = end; + } } void CSMWorld::InfoTableProxyModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) From b667500ae2702116222ce3a99e3ac033134ac03c Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 2 Jul 2015 15:47:12 +0300 Subject: [PATCH 108/441] Add the configuration widget for extended commands Conflicts: apps/opencs/CMakeLists.txt --- apps/opencs/CMakeLists.txt | 2 +- .../world/extendedcommandconfigurator.cpp | 184 ++++++++++++++++++ .../world/extendedcommandconfigurator.hpp | 68 +++++++ 3 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/view/world/extendedcommandconfigurator.cpp create mode 100644 apps/opencs/view/world/extendedcommandconfigurator.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 70abe53fa..1ae32bcbb 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -64,7 +64,7 @@ opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable - dialoguespinbox recordbuttonbar tableeditidaction scripterrortable + dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator ) opencs_units_noqt (view/world diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp new file mode 100644 index 000000000..dfdb5f573 --- /dev/null +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -0,0 +1,184 @@ +#include "extendedcommandconfigurator.hpp" + +#include +#include +#include +#include + +#include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/universalid.hpp" + +namespace +{ + QString getTypeGroupTitle(CSVWorld::ExtendedCommandConfigurator::Mode mode) + { + static const QString title = "Tables affected by "; + QString titleSuffix = "Extended Delete"; + if (mode == CSVWorld::ExtendedCommandConfigurator::Mode_Revert) + { + titleSuffix = "Extended Revert"; + } + return title + titleSuffix; + } +} + +CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Document &document, + const CSMWorld::UniversalId &id, + QWidget *parent) + : QWidget(parent), + mNumUsedCheckBoxes(0), + mMode(Mode_None) +{ + mCommandDispatcher = new CSMWorld::CommandDispatcher(document, id, this); + + mPerformButton = new QPushButton("Perform", this); + mPerformButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(mPerformButton, SIGNAL(clicked(bool)), this, SLOT(performExtendedCommand())); + + mCancelButton = new QPushButton("Cancel", this); + mCancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(mCancelButton, SIGNAL(clicked(bool)), this, SLOT(done())); + + mButtonLayout = new QHBoxLayout(); + mButtonLayout->setAlignment(Qt::AlignCenter); + mButtonLayout->addWidget(mPerformButton); + mButtonLayout->addWidget(mCancelButton); + + mTypeGroup = new QGroupBox(this); + + QGridLayout *groupLayout = new QGridLayout(mTypeGroup); + groupLayout->setAlignment(Qt::AlignCenter); + mTypeGroup->setLayout(groupLayout); + + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setSizeConstraint(QLayout::SetNoConstraint); + mainLayout->addWidget(mTypeGroup); + mainLayout->addLayout(mButtonLayout); +} + +CSVWorld::ExtendedCommandConfigurator::~ExtendedCommandConfigurator() +{ + delete mButtonLayout; +} + +void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandConfigurator::Mode mode) +{ + mMode = mode; + if (mMode != Mode_None) + { + mTypeGroup->setTitle(getTypeGroupTitle(mMode)); + setupCheckBoxes(mCommandDispatcher->getExtendedTypes()); + setupGroupLayout(); + } +} + +void CSVWorld::ExtendedCommandConfigurator::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + setupGroupLayout(); +} + +void CSVWorld::ExtendedCommandConfigurator::setupGroupLayout() +{ + if (mMode == Mode_None) + { + return; + } + + int groupWidth = mTypeGroup->geometry().width(); + QGridLayout *layout = qobject_cast(mTypeGroup->layout()); + + // One row of checkboxes with enough space - the setup is over + if (mNumUsedCheckBoxes > 0 && layout->rowCount() == 1 && groupWidth >= mTypeGroup->sizeHint().width()) + { + return; + } + + // Find the optimal number of rows to place the checkboxes within the available space + int divider = 1; + do + { + while (layout->itemAt(0) != NULL) + { + layout->removeItem(layout->itemAt(0)); + } + + int counter = 0; + int itemsPerRow = mNumUsedCheckBoxes / divider; + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (current->first->isVisible()) + { + int row = counter / itemsPerRow; + int column = counter - (counter / itemsPerRow) * itemsPerRow; + layout->addWidget(current->first, row, column); + } + ++counter; + } + divider *= 2; + } + while (groupWidth < mTypeGroup->sizeHint().width()); +} + +void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vector &types) +{ + // Make sure that we have enough checkboxes + int numTypes = static_cast(types.size()); + int numCheckBoxes = static_cast(mTypeCheckBoxes.size()); + if (numTypes > numCheckBoxes) + { + for (int i = numTypes - numCheckBoxes; i > 0; --i) + { + mTypeCheckBoxes.insert(std::make_pair(new QCheckBox(this), CSMWorld::UniversalId::Type_None)); + } + } + + // Set up the checkboxes + int counter = 0; + CheckBoxMap::iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (counter < numTypes) + { + CSMWorld::UniversalId type = types[counter]; + current->first->setText(QString::fromUtf8(type.getTypeName().c_str())); + current->first->setChecked(true); + current->second = type; + ++counter; + } + else + { + current->first->hide(); + } + } + mNumUsedCheckBoxes = counter - 1; +} + +void CSVWorld::ExtendedCommandConfigurator::performExtendedCommand() +{ + std::vector types; + + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (current->first->isChecked()) + { + types.push_back(current->second); + } + } + + mCommandDispatcher->setExtendedTypes(types); + if (mMode == Mode_Delete) + { + mCommandDispatcher->executeExtendedDelete(); + } + else + { + mCommandDispatcher->executeExtendedRevert(); + } + emit done(); +} diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp new file mode 100644 index 000000000..2feec14a7 --- /dev/null +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -0,0 +1,68 @@ +#ifndef CSVWORLD_EXTENDEDCOMMANDCONFIGURATOR_HPP +#define CSVWORLD_EXTENDEDCOMMANDCONFIGURATOR_HPP + +#include + +#include + +class QPushButton; +class QGroupBox; +class QCheckBox; +class QHBoxLayout; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; + class UniversalId; +} + +namespace CSVWorld +{ + class ExtendedCommandConfigurator : public QWidget + { + Q_OBJECT + + public: + enum Mode { Mode_None, Mode_Delete, Mode_Revert }; + + private: + typedef std::map CheckBoxMap; + + QPushButton *mPerformButton; + QPushButton *mCancelButton; + QHBoxLayout *mButtonLayout; + QGroupBox *mTypeGroup; + CheckBoxMap mTypeCheckBoxes; + int mNumUsedCheckBoxes; + + Mode mMode; + CSMWorld::CommandDispatcher *mCommandDispatcher; + + void setupGroupLayout(); + void setupCheckBoxes(const std::vector &types); + + public: + ExtendedCommandConfigurator(CSMDoc::Document &document, + const CSMWorld::UniversalId &id, + QWidget *parent = 0); + virtual ~ExtendedCommandConfigurator(); + + void configure(Mode mode); + + protected: + virtual void resizeEvent(QResizeEvent *event); + + private slots: + void performExtendedCommand(); + + signals: + void done(); + }; +} + +#endif From 297373f436900b733bc991238c86b2ee331df222 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 2 Jul 2015 17:21:47 +0300 Subject: [PATCH 109/441] Add the configuration widget to the bottom box Conflicts: apps/opencs/view/world/tablebottombox.cpp --- apps/opencs/view/world/tablebottombox.cpp | 37 +++++++++++++++++------ apps/opencs/view/world/tablebottombox.hpp | 17 +++++++++-- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index 12226450b..4e99058d5 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -47,11 +47,19 @@ void CSVWorld::TableBottomBox::updateStatus() } } +void CSVWorld::TableBottomBox::extendedConfigRequest(CSVWorld::ExtendedCommandConfigurator::Mode mode) +{ + mExtendedConfigurator->configure (mode); + mLayout->setCurrentWidget (mExtendedConfigurator); + mEditMode = EditMode_ExtendedConfig; + setVisible (true); +} + CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, CSMDoc::Document& document, const CSMWorld::UniversalId& id, QWidget *parent) -: QWidget (parent), mShowStatusBar (false), mCreating (false), mHasPosition (false) +: QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition (false) { for (int i=0; i<4; ++i) mStatusCount[i] = 0; @@ -77,13 +85,15 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto { mLayout->addWidget (mCreator); - connect (mCreator, SIGNAL (done()), this, SLOT (createRequestDone())); + connect (mCreator, SIGNAL (done()), this, SLOT (requestDone())); connect (mCreator, SIGNAL (requestFocus (const std::string&)), this, SIGNAL (requestFocus (const std::string&))); } - setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Fixed); + mExtendedConfigurator = new ExtendedCommandConfigurator (document, id, this); + mLayout->addWidget (mExtendedConfigurator); + connect (mExtendedConfigurator, SIGNAL (done()), this, SLOT (requestDone())); } void CSVWorld::TableBottomBox::setEditLock (bool locked) @@ -101,7 +111,7 @@ void CSVWorld::TableBottomBox::setStatusBar (bool show) { if (show!=mShowStatusBar) { - setVisible (show || mCreating); + setVisible (show || (mEditMode != EditMode_None)); mShowStatusBar = show; @@ -115,7 +125,7 @@ bool CSVWorld::TableBottomBox::canCreateAndDelete() const return mCreator; } -void CSVWorld::TableBottomBox::createRequestDone() +void CSVWorld::TableBottomBox::requestDone() { if (!mShowStatusBar) setVisible (false); @@ -123,8 +133,7 @@ void CSVWorld::TableBottomBox::createRequestDone() updateStatus(); mLayout->setCurrentWidget (mStatusBar); - - mCreating = false; + mEditMode = EditMode_None; } void CSVWorld::TableBottomBox::selectionSizeChanged (int size) @@ -182,7 +191,7 @@ void CSVWorld::TableBottomBox::createRequest() mCreator->toggleWidgets(true); mLayout->setCurrentWidget (mCreator); setVisible (true); - mCreating = true; + mEditMode = EditMode_Creation; mCreator->focus(); } @@ -194,6 +203,16 @@ void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, mLayout->setCurrentWidget(mCreator); mCreator->toggleWidgets(false); setVisible (true); - mCreating = true; + mEditMode = EditMode_Creation; mCreator->focus(); } + +void CSVWorld::TableBottomBox::extendedDeleteConfigRequest() +{ + extendedConfigRequest(ExtendedCommandConfigurator::Mode_Delete); +} + +void CSVWorld::TableBottomBox::extendedRevertConfigRequest() +{ + extendedConfigRequest(ExtendedCommandConfigurator::Mode_Revert); +} diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index 6e68553bc..5e2680ede 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -4,10 +4,11 @@ #include #include +#include "extendedcommandconfigurator.hpp" + class QLabel; class QStackedLayout; class QStatusBar; -class QUndoStack; namespace CSMDoc { @@ -23,12 +24,17 @@ namespace CSVWorld { Q_OBJECT + enum EditMode { EditMode_None, EditMode_Creation, EditMode_ExtendedConfig }; + bool mShowStatusBar; QLabel *mStatus; QStatusBar *mStatusBar; int mStatusCount[4]; + + EditMode mEditMode; Creator *mCreator; - bool mCreating; + ExtendedCommandConfigurator *mExtendedConfigurator; + QStackedLayout *mLayout; bool mHasPosition; int mRow; @@ -42,6 +48,8 @@ namespace CSVWorld void updateStatus(); + void extendedConfigRequest(ExtendedCommandConfigurator::Mode mode); + public: TableBottomBox (const CreatorFactoryBase& creatorFactory, @@ -68,7 +76,7 @@ namespace CSVWorld private slots: - void createRequestDone(); + void requestDone(); ///< \note This slot being called does not imply success. public slots: @@ -87,6 +95,9 @@ namespace CSVWorld void createRequest(); void cloneRequest(const std::string& id, const CSMWorld::UniversalId::Type type); + + void extendedDeleteConfigRequest(); + void extendedRevertConfigRequest(); }; } From 832e910b6f693b99164ae70790ef98af8fb67373 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 2 Jul 2015 20:41:32 +0300 Subject: [PATCH 110/441] Add the user setting for enabling the configuration of extended commands --- apps/opencs/model/settings/usersettings.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 30ebdce7f..56991c050 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -242,6 +242,12 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() "Jump to the added or cloned record."); jumpToAdded->setDefaultValue (defaultValue); jumpToAdded->setDeclaredValues (jumpValues); + + Setting *extendedConfig = createSetting (Type_CheckBox, "extended-config", + "Enable configuration of the extended delete/revert"); + extendedConfig->setDefaultValue("false"); + extendedConfig->setToolTip("Enables the ability to specify tables that will be affected by " + "the extended delete/revert command"); } declareSection ("dialogues", "ID Dialogues"); From 94725f32d942c8f90079eb06639a97c2c4add6b5 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 2 Jul 2015 20:44:59 +0300 Subject: [PATCH 111/441] Add the ability to configure extended commands for tables --- .../world/extendedcommandconfigurator.cpp | 6 +- .../world/extendedcommandconfigurator.hpp | 2 +- apps/opencs/view/world/table.cpp | 70 +++++++++++++------ apps/opencs/view/world/table.hpp | 10 +++ apps/opencs/view/world/tablebottombox.cpp | 13 ++-- apps/opencs/view/world/tablebottombox.hpp | 7 +- apps/opencs/view/world/tablesubview.cpp | 5 ++ 7 files changed, 79 insertions(+), 34 deletions(-) diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index dfdb5f573..6ba26634b 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -37,7 +37,7 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum mCancelButton = new QPushButton("Cancel", this); mCancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - connect(mCancelButton, SIGNAL(clicked(bool)), this, SLOT(done())); + connect(mCancelButton, SIGNAL(clicked(bool)), this, SIGNAL(done())); mButtonLayout = new QHBoxLayout(); mButtonLayout->setAlignment(Qt::AlignCenter); @@ -61,12 +61,14 @@ CSVWorld::ExtendedCommandConfigurator::~ExtendedCommandConfigurator() delete mButtonLayout; } -void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandConfigurator::Mode mode) +void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds) { mMode = mode; if (mMode != Mode_None) { mTypeGroup->setTitle(getTypeGroupTitle(mMode)); + mCommandDispatcher->setSelection(selectedIds); setupCheckBoxes(mCommandDispatcher->getExtendedTypes()); setupGroupLayout(); } diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp index 2feec14a7..dd2477444 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.hpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -52,7 +52,7 @@ namespace CSVWorld QWidget *parent = 0); virtual ~ExtendedCommandConfigurator(); - void configure(Mode mode); + void configure(Mode mode, const std::vector &selectedIds); protected: virtual void resizeEvent(QResizeEvent *event); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 3d86323e1..b4b8c402a 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -36,28 +36,14 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { // configure dispatcher - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - std::vector records; - - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); - - records.push_back (mModel->data ( - mModel->index (row, columnIndex)).toString().toUtf8().constData()); - } - - mDispatcher->setSelection (records); + mDispatcher->setSelection (getSelectedIds()); std::vector extendedTypes = mDispatcher->getExtendedTypes(); mDispatcher->setExtendedTypes (extendedTypes); // create context menu + QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu (this); /// \todo add menu items for select all and clear selection @@ -355,16 +341,12 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); addAction (mPreviewAction); - /// \todo add a user option, that redirects the extended action to an input panel (in - /// the bottom bar) that lets the user select which record collections should be - /// modified. - mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); - connect (mExtendedDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedDelete())); + connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete())); addAction (mExtendedDeleteAction); mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); - connect (mExtendedRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedRevert())); + connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert())); addAction (mExtendedRevertAction); mEditIdAction = new TableEditIdAction (*this, this); @@ -434,6 +416,22 @@ CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData()); } +std::vector CSVWorld::Table::getSelectedIds() const +{ + std::vector ids; + QModelIndexList selectedRows = selectionModel()->selectedRows(); + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); + iter != selectedRows.end(); + ++iter) + { + int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); + ids.push_back (mModel->data (mModel->index (row, columnIndex)).toString().toUtf8().constData()); + } + return ids; +} + void CSVWorld::Table::editRecord() { if (!mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) @@ -566,6 +564,34 @@ void CSVWorld::Table::previewRecord() } } +void CSVWorld::Table::executeExtendedDelete() +{ + CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); + QString configSetting = settings.settingValue ("table-input/extended-config"); + if (configSetting == "true") + { + emit extendedDeleteConfigRequest(getSelectedIds()); + } + else + { + QMetaObject::invokeMethod(mDispatcher, "executeExtendedDelete", Qt::QueuedConnection); + } +} + +void CSVWorld::Table::executeExtendedRevert() +{ + CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); + QString configSetting = settings.settingValue ("table-input/extended-config"); + if (configSetting == "true") + { + emit extendedRevertConfigRequest(getSelectedIds()); + } + else + { + QMetaObject::invokeMethod(mDispatcher, "executeExtendedRevert", Qt::QueuedConnection); + } +} + void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList &list) { if (name=="table-input/jump-to-added") diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 1c25832bd..0b7efef9d 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -97,6 +97,8 @@ namespace CSVWorld std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; + std::vector getSelectedIds() const; + virtual std::vector getDraggedRecords() const; signals: @@ -116,6 +118,10 @@ namespace CSVWorld void closeRequest(); + void extendedDeleteConfigRequest(const std::vector &selectedIds); + + void extendedRevertConfigRequest(const std::vector &selectedIds); + private slots: void editCell(); @@ -132,6 +138,10 @@ namespace CSVWorld void previewRecord(); + void executeExtendedDelete(); + + void executeExtendedRevert(); + public slots: void tableSizeUpdate(); diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index 4e99058d5..589a56cee 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -47,9 +47,10 @@ void CSVWorld::TableBottomBox::updateStatus() } } -void CSVWorld::TableBottomBox::extendedConfigRequest(CSVWorld::ExtendedCommandConfigurator::Mode mode) +void CSVWorld::TableBottomBox::extendedConfigRequest(CSVWorld::ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds) { - mExtendedConfigurator->configure (mode); + mExtendedConfigurator->configure (mode, selectedIds); mLayout->setCurrentWidget (mExtendedConfigurator); mEditMode = EditMode_ExtendedConfig; setVisible (true); @@ -207,12 +208,12 @@ void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, mCreator->focus(); } -void CSVWorld::TableBottomBox::extendedDeleteConfigRequest() +void CSVWorld::TableBottomBox::extendedDeleteConfigRequest(const std::vector &selectedIds) { - extendedConfigRequest(ExtendedCommandConfigurator::Mode_Delete); + extendedConfigRequest(ExtendedCommandConfigurator::Mode_Delete, selectedIds); } -void CSVWorld::TableBottomBox::extendedRevertConfigRequest() +void CSVWorld::TableBottomBox::extendedRevertConfigRequest(const std::vector &selectedIds) { - extendedConfigRequest(ExtendedCommandConfigurator::Mode_Revert); + extendedConfigRequest(ExtendedCommandConfigurator::Mode_Revert, selectedIds); } diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index 5e2680ede..542989927 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -48,7 +48,8 @@ namespace CSVWorld void updateStatus(); - void extendedConfigRequest(ExtendedCommandConfigurator::Mode mode); + void extendedConfigRequest(ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds); public: @@ -96,8 +97,8 @@ namespace CSVWorld void cloneRequest(const std::string& id, const CSMWorld::UniversalId::Type type); - void extendedDeleteConfigRequest(); - void extendedRevertConfigRequest(); + void extendedDeleteConfigRequest(const std::vector &selectedIds); + void extendedRevertConfigRequest(const std::vector &selectedIds); }; } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index ca551c53f..f20d79036 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -89,6 +89,11 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); + + connect (mTable, SIGNAL(extendedDeleteConfigRequest(const std::vector &)), + mBottom, SLOT(extendedDeleteConfigRequest(const std::vector &))); + connect (mTable, SIGNAL(extendedRevertConfigRequest(const std::vector &)), + mBottom, SLOT(extendedRevertConfigRequest(const std::vector &))); } connect (mBottom, SIGNAL (requestFocus (const std::string&)), mTable, SLOT (requestFocus (const std::string&))); From 6c51e139172a702078791ea6211f1d5d862a6ca0 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 2 Jul 2015 22:05:45 +0300 Subject: [PATCH 112/441] Rework widget layout of ExtendedCommandConfigurator --- .../world/extendedcommandconfigurator.cpp | 36 ++++++++----------- .../world/extendedcommandconfigurator.hpp | 4 +-- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index 6ba26634b..9f067545c 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "../../model/world/commanddispatcher.hpp" @@ -39,10 +40,8 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum mCancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(mCancelButton, SIGNAL(clicked(bool)), this, SIGNAL(done())); - mButtonLayout = new QHBoxLayout(); - mButtonLayout->setAlignment(Qt::AlignCenter); - mButtonLayout->addWidget(mPerformButton); - mButtonLayout->addWidget(mCancelButton); + mCommandTitle = new QLabel(this); + mCommandTitle->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mTypeGroup = new QGroupBox(this); @@ -50,15 +49,12 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum groupLayout->setAlignment(Qt::AlignCenter); mTypeGroup->setLayout(groupLayout); - QVBoxLayout *mainLayout = new QVBoxLayout(this); + QHBoxLayout *mainLayout = new QHBoxLayout(this); mainLayout->setSizeConstraint(QLayout::SetNoConstraint); + mainLayout->addWidget(mCommandTitle); mainLayout->addWidget(mTypeGroup); - mainLayout->addLayout(mButtonLayout); -} - -CSVWorld::ExtendedCommandConfigurator::~ExtendedCommandConfigurator() -{ - delete mButtonLayout; + mainLayout->addWidget(mPerformButton); + mainLayout->addWidget(mCancelButton); } void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandConfigurator::Mode mode, @@ -67,7 +63,9 @@ void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandC mMode = mode; if (mMode != Mode_None) { - mTypeGroup->setTitle(getTypeGroupTitle(mMode)); + QString title = (mMode == Mode_Delete) ? "Extended Delete" : "Extended Revert"; + title.append(" from:"); + mCommandTitle->setText(title); mCommandDispatcher->setSelection(selectedIds); setupCheckBoxes(mCommandDispatcher->getExtendedTypes()); setupGroupLayout(); @@ -90,12 +88,6 @@ void CSVWorld::ExtendedCommandConfigurator::setupGroupLayout() int groupWidth = mTypeGroup->geometry().width(); QGridLayout *layout = qobject_cast(mTypeGroup->layout()); - // One row of checkboxes with enough space - the setup is over - if (mNumUsedCheckBoxes > 0 && layout->rowCount() == 1 && groupWidth >= mTypeGroup->sizeHint().width()) - { - return; - } - // Find the optimal number of rows to place the checkboxes within the available space int divider = 1; do @@ -111,7 +103,7 @@ void CSVWorld::ExtendedCommandConfigurator::setupGroupLayout() CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); for (; current != end; ++current) { - if (current->first->isVisible()) + if (counter < mNumUsedCheckBoxes) { int row = counter / itemsPerRow; int column = counter - (counter / itemsPerRow) * itemsPerRow; @@ -121,7 +113,7 @@ void CSVWorld::ExtendedCommandConfigurator::setupGroupLayout() } divider *= 2; } - while (groupWidth < mTypeGroup->sizeHint().width()); + while (groupWidth < mTypeGroup->sizeHint().width() && divider <= mNumUsedCheckBoxes); } void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vector &types) @@ -133,7 +125,7 @@ void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vector 0; --i) { - mTypeCheckBoxes.insert(std::make_pair(new QCheckBox(this), CSMWorld::UniversalId::Type_None)); + mTypeCheckBoxes.insert(std::make_pair(new QCheckBox(mTypeGroup), CSMWorld::UniversalId::Type_None)); } } @@ -156,7 +148,7 @@ void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vectorfirst->hide(); } } - mNumUsedCheckBoxes = counter - 1; + mNumUsedCheckBoxes = numTypes; } void CSVWorld::ExtendedCommandConfigurator::performExtendedCommand() diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp index dd2477444..8efe36fa7 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.hpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -8,6 +8,7 @@ class QPushButton; class QGroupBox; class QCheckBox; +class QLabel; class QHBoxLayout; namespace CSMDoc @@ -35,7 +36,7 @@ namespace CSVWorld QPushButton *mPerformButton; QPushButton *mCancelButton; - QHBoxLayout *mButtonLayout; + QLabel *mCommandTitle; QGroupBox *mTypeGroup; CheckBoxMap mTypeCheckBoxes; int mNumUsedCheckBoxes; @@ -50,7 +51,6 @@ namespace CSVWorld ExtendedCommandConfigurator(CSMDoc::Document &document, const CSMWorld::UniversalId &id, QWidget *parent = 0); - virtual ~ExtendedCommandConfigurator(); void configure(Mode mode, const std::vector &selectedIds); From 24eb9bddfb2b2195f551a6c5c3b8a93d5a212622 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 22:07:37 +0300 Subject: [PATCH 113/441] TableBottomBox adjusts its size according to the current widget size --- apps/opencs/view/world/tablebottombox.cpp | 22 ++++++++++++++++++++++ apps/opencs/view/world/tablebottombox.hpp | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index 589a56cee..94ecaa051 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -9,6 +9,20 @@ #include "creator.hpp" +void CSVWorld::TableBottomBox::updateSize() +{ + // Make sure that the size of the bottom box is determined by the currently visible widget + for (int i = 0; i < mLayout->count(); ++i) + { + QSizePolicy::Policy verPolicy = QSizePolicy::Ignored; + if (mLayout->widget(i) == mLayout->currentWidget()) + { + verPolicy = QSizePolicy::Expanding; + } + mLayout->widget(i)->setSizePolicy(QSizePolicy::Expanding, verPolicy); + } +} + void CSVWorld::TableBottomBox::updateStatus() { if (mShowStatusBar) @@ -69,6 +83,7 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto mLayout = new QStackedLayout; mLayout->setContentsMargins (0, 0, 0, 0); + connect (mLayout, SIGNAL (currentChanged (int)), this, SLOT (currentWidgetChanged (int))); mStatus = new QLabel; @@ -95,6 +110,8 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto mExtendedConfigurator = new ExtendedCommandConfigurator (document, id, this); mLayout->addWidget (mExtendedConfigurator); connect (mExtendedConfigurator, SIGNAL (done()), this, SLOT (requestDone())); + + updateSize(); } void CSVWorld::TableBottomBox::setEditLock (bool locked) @@ -137,6 +154,11 @@ void CSVWorld::TableBottomBox::requestDone() mEditMode = EditMode_None; } +void CSVWorld::TableBottomBox::currentWidgetChanged(int /*index*/) +{ + updateSize(); +} + void CSVWorld::TableBottomBox::selectionSizeChanged (int size) { if (mStatusCount[3]!=size) diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index 542989927..551998c9e 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -46,6 +46,8 @@ namespace CSVWorld TableBottomBox (const TableBottomBox&); TableBottomBox& operator= (const TableBottomBox&); + void updateSize(); + void updateStatus(); void extendedConfigRequest(ExtendedCommandConfigurator::Mode mode, @@ -80,6 +82,8 @@ namespace CSVWorld void requestDone(); ///< \note This slot being called does not imply success. + void currentWidgetChanged(int index); + public slots: void selectionSizeChanged (int size); From bf3891e16f9912bcbc6b7f0886ca1f8ed43a1562 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 22:25:56 +0300 Subject: [PATCH 114/441] ExtendedCommandConfigurator: disable the perform button when all tables are unchecked --- .../world/extendedcommandconfigurator.cpp | 24 +++++++++++++++++-- .../world/extendedcommandconfigurator.hpp | 2 ++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index 9f067545c..257e9c4f0 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -28,6 +28,7 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum QWidget *parent) : QWidget(parent), mNumUsedCheckBoxes(0), + mNumChecked(0), mMode(Mode_None) { mCommandDispatcher = new CSMWorld::CommandDispatcher(document, id, this); @@ -125,7 +126,9 @@ void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vector 0; --i) { - mTypeCheckBoxes.insert(std::make_pair(new QCheckBox(mTypeGroup), CSMWorld::UniversalId::Type_None)); + QCheckBox *checkBox = new QCheckBox(mTypeGroup); + connect(checkBox, SIGNAL(stateChanged(int)), this, SLOT(checkBoxStateChanged(int))); + mTypeCheckBoxes.insert(std::make_pair(checkBox, CSMWorld::UniversalId::Type_None)); } } @@ -148,7 +151,7 @@ void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vectorfirst->hide(); } } - mNumUsedCheckBoxes = numTypes; + mNumChecked = mNumUsedCheckBoxes = numTypes; } void CSVWorld::ExtendedCommandConfigurator::performExtendedCommand() @@ -176,3 +179,20 @@ void CSVWorld::ExtendedCommandConfigurator::performExtendedCommand() } emit done(); } + +void CSVWorld::ExtendedCommandConfigurator::checkBoxStateChanged(int state) +{ + switch (state) + { + case Qt::Unchecked: + --mNumChecked; + break; + case Qt::Checked: + ++mNumChecked; + break; + case Qt::PartiallyChecked: // Not used + break; + } + + mPerformButton->setEnabled(mNumChecked > 0); +} diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp index 8efe36fa7..6a5e1e2e7 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.hpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -40,6 +40,7 @@ namespace CSVWorld QGroupBox *mTypeGroup; CheckBoxMap mTypeCheckBoxes; int mNumUsedCheckBoxes; + int mNumChecked; Mode mMode; CSMWorld::CommandDispatcher *mCommandDispatcher; @@ -59,6 +60,7 @@ namespace CSVWorld private slots: void performExtendedCommand(); + void checkBoxStateChanged(int state); signals: void done(); From 8eb677befb5134aae8c35f835c2aa8261ec2d09a Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 22:31:38 +0300 Subject: [PATCH 115/441] The configuration panel gets a focus when opening --- apps/opencs/view/world/tablebottombox.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index 94ecaa051..021119e48 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -68,6 +68,7 @@ void CSVWorld::TableBottomBox::extendedConfigRequest(CSVWorld::ExtendedCommandCo mLayout->setCurrentWidget (mExtendedConfigurator); mEditMode = EditMode_ExtendedConfig; setVisible (true); + mExtendedConfigurator->setFocus(); } CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, From 09d867c9bfc84729366a0af80bf3ba9cb7e2fdff Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 23:15:26 +0300 Subject: [PATCH 116/441] ExtendedCommandConfigurator: the perform button takes the name of the command that is executed --- .../world/extendedcommandconfigurator.cpp | 24 ++----------------- .../world/extendedcommandconfigurator.hpp | 1 - 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index 257e9c4f0..36bff7d59 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -9,20 +9,6 @@ #include "../../model/world/commanddispatcher.hpp" #include "../../model/world/universalid.hpp" -namespace -{ - QString getTypeGroupTitle(CSVWorld::ExtendedCommandConfigurator::Mode mode) - { - static const QString title = "Tables affected by "; - QString titleSuffix = "Extended Delete"; - if (mode == CSVWorld::ExtendedCommandConfigurator::Mode_Revert) - { - titleSuffix = "Extended Revert"; - } - return title + titleSuffix; - } -} - CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Document &document, const CSMWorld::UniversalId &id, QWidget *parent) @@ -33,7 +19,7 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum { mCommandDispatcher = new CSMWorld::CommandDispatcher(document, id, this); - mPerformButton = new QPushButton("Perform", this); + mPerformButton = new QPushButton(this); mPerformButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(mPerformButton, SIGNAL(clicked(bool)), this, SLOT(performExtendedCommand())); @@ -41,9 +27,6 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum mCancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(mCancelButton, SIGNAL(clicked(bool)), this, SIGNAL(done())); - mCommandTitle = new QLabel(this); - mCommandTitle->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - mTypeGroup = new QGroupBox(this); QGridLayout *groupLayout = new QGridLayout(mTypeGroup); @@ -52,7 +35,6 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum QHBoxLayout *mainLayout = new QHBoxLayout(this); mainLayout->setSizeConstraint(QLayout::SetNoConstraint); - mainLayout->addWidget(mCommandTitle); mainLayout->addWidget(mTypeGroup); mainLayout->addWidget(mPerformButton); mainLayout->addWidget(mCancelButton); @@ -64,9 +46,7 @@ void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandC mMode = mode; if (mMode != Mode_None) { - QString title = (mMode == Mode_Delete) ? "Extended Delete" : "Extended Revert"; - title.append(" from:"); - mCommandTitle->setText(title); + mPerformButton->setText((mMode == Mode_Delete) ? "Extended Delete" : "Extended Revert"); mCommandDispatcher->setSelection(selectedIds); setupCheckBoxes(mCommandDispatcher->getExtendedTypes()); setupGroupLayout(); diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp index 6a5e1e2e7..9ed16aa1e 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.hpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -36,7 +36,6 @@ namespace CSVWorld QPushButton *mPerformButton; QPushButton *mCancelButton; - QLabel *mCommandTitle; QGroupBox *mTypeGroup; CheckBoxMap mTypeCheckBoxes; int mNumUsedCheckBoxes; From 977f317eb926c93b9fcd1839dcd7ae5ecf99c39d Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 3 Jul 2015 23:35:00 +0300 Subject: [PATCH 117/441] ExtendedCommandConfigurator: the perform button is now the default for the widget --- apps/opencs/view/world/extendedcommandconfigurator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index 36bff7d59..85c5a2bda 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -20,6 +20,7 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum mCommandDispatcher = new CSMWorld::CommandDispatcher(document, id, this); mPerformButton = new QPushButton(this); + mPerformButton->setDefault(true); mPerformButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(mPerformButton, SIGNAL(clicked(bool)), this, SLOT(performExtendedCommand())); From 214a448ecf05bdb7e798c7d7bd966220f086d039 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 02:02:22 +0300 Subject: [PATCH 118/441] Fix build error --- apps/opencs/view/world/extendedcommandconfigurator.cpp | 1 - apps/opencs/view/world/extendedcommandconfigurator.hpp | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index 85c5a2bda..d3b97b127 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -7,7 +7,6 @@ #include #include "../../model/world/commanddispatcher.hpp" -#include "../../model/world/universalid.hpp" CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Document &document, const CSMWorld::UniversalId &id, diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp index 9ed16aa1e..590cabab1 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.hpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -5,6 +5,8 @@ #include +#include "../../model/world/universalid.hpp" + class QPushButton; class QGroupBox; class QCheckBox; @@ -19,7 +21,6 @@ namespace CSMDoc namespace CSMWorld { class CommandDispatcher; - class UniversalId; } namespace CSVWorld From 194888e035f0f4a91b0e66db7a601c5c6edd0a8d Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 20:30:02 +0300 Subject: [PATCH 119/441] TableBottomBox: Creator/Configurator can be closed via Escape --- apps/opencs/view/world/tablebottombox.cpp | 18 ++++++++++++++++++ apps/opencs/view/world/tablebottombox.hpp | 2 ++ 2 files changed, 20 insertions(+) diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index 021119e48..c56c392d8 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include "creator.hpp" @@ -100,6 +102,7 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto if (mCreator) { + mCreator->installEventFilter(this); mLayout->addWidget (mCreator); connect (mCreator, SIGNAL (done()), this, SLOT (requestDone())); @@ -109,6 +112,7 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto } mExtendedConfigurator = new ExtendedCommandConfigurator (document, id, this); + mExtendedConfigurator->installEventFilter(this); mLayout->addWidget (mExtendedConfigurator); connect (mExtendedConfigurator, SIGNAL (done()), this, SLOT (requestDone())); @@ -126,6 +130,20 @@ CSVWorld::TableBottomBox::~TableBottomBox() delete mCreator; } +bool CSVWorld::TableBottomBox::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) + { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) + { + requestDone(); + return true; + } + } + return QWidget::eventFilter(object, event); +} + void CSVWorld::TableBottomBox::setStatusBar (bool show) { if (show!=mShowStatusBar) diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index 551998c9e..781cccc9e 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -62,6 +62,8 @@ namespace CSVWorld virtual ~TableBottomBox(); + virtual bool eventFilter(QObject *object, QEvent *event); + void setEditLock (bool locked); void setStatusBar (bool show); From be1cf2fc805d101dee099d132fc0ef013211442a Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 4 Jul 2015 20:47:10 +0300 Subject: [PATCH 120/441] ExtendedCommandConfigurator: layout changes --- apps/opencs/view/world/extendedcommandconfigurator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index d3b97b127..b9b2fa10c 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -30,11 +30,12 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum mTypeGroup = new QGroupBox(this); QGridLayout *groupLayout = new QGridLayout(mTypeGroup); - groupLayout->setAlignment(Qt::AlignCenter); + groupLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); mTypeGroup->setLayout(groupLayout); QHBoxLayout *mainLayout = new QHBoxLayout(this); mainLayout->setSizeConstraint(QLayout::SetNoConstraint); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->addWidget(mTypeGroup); mainLayout->addWidget(mPerformButton); mainLayout->addWidget(mCancelButton); From 34d8e5dba5402c198d5eb30c50569e8cb768fea7 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 5 Jul 2015 20:11:21 +0300 Subject: [PATCH 121/441] Rewording of the user setting for extended configuration --- apps/opencs/model/settings/usersettings.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 56991c050..a40a790b7 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -244,10 +244,13 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() jumpToAdded->setDeclaredValues (jumpValues); Setting *extendedConfig = createSetting (Type_CheckBox, "extended-config", - "Enable configuration of the extended delete/revert"); + "Manually specify affected record types for an extended delete/revert"); extendedConfig->setDefaultValue("false"); - extendedConfig->setToolTip("Enables the ability to specify tables that will be affected by " - "the extended delete/revert command"); + extendedConfig->setToolTip("Delete and revert commands have an extended form that also affects " + "associated records.\n\n" + "If this option is enabled, types of affected records are selected " + "manually before a command execution.\nOtherwise, all associated " + "records are deleted/reverted immediately."); } declareSection ("dialogues", "ID Dialogues"); From 6033e67b4ada7cd194c2a940960d57b3fa7b0c5b Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 5 Jul 2015 22:10:37 +0300 Subject: [PATCH 122/441] Close the extended configurator when all respective records were removed outside --- .../world/extendedcommandconfigurator.cpp | 40 ++++++++++++++++++- .../world/extendedcommandconfigurator.hpp | 4 ++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index b9b2fa10c..d243ff256 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -1,12 +1,17 @@ #include "extendedcommandconfigurator.hpp" +#include + #include #include #include #include #include +#include "../../model/doc/document.hpp" + #include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/data.hpp" CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Document &document, const CSMWorld::UniversalId &id, @@ -14,10 +19,13 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum : QWidget(parent), mNumUsedCheckBoxes(0), mNumChecked(0), - mMode(Mode_None) + mMode(Mode_None), + mData(document.getData()) { mCommandDispatcher = new CSMWorld::CommandDispatcher(document, id, this); + connect(&mData, SIGNAL(idListChanged()), this, SLOT(dataIdListChanged())); + mPerformButton = new QPushButton(this); mPerformButton->setDefault(true); mPerformButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); @@ -48,7 +56,8 @@ void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandC if (mMode != Mode_None) { mPerformButton->setText((mMode == Mode_Delete) ? "Extended Delete" : "Extended Revert"); - mCommandDispatcher->setSelection(selectedIds); + mSelectedIds = selectedIds; + mCommandDispatcher->setSelection(mSelectedIds); setupCheckBoxes(mCommandDispatcher->getExtendedTypes()); setupGroupLayout(); } @@ -177,3 +186,30 @@ void CSVWorld::ExtendedCommandConfigurator::checkBoxStateChanged(int state) mPerformButton->setEnabled(mNumChecked > 0); } + +void CSVWorld::ExtendedCommandConfigurator::dataIdListChanged() +{ + bool idsRemoved = false; + for (int i = 0; i < static_cast(mSelectedIds.size()); ++i) + { + if (!mData.hasId(mSelectedIds[i])) + { + std::swap(mSelectedIds[i], mSelectedIds.back()); + mSelectedIds.pop_back(); + idsRemoved = true; + --i; + } + } + + // If all selected IDs were removed, cancel the configurator + if (mSelectedIds.empty()) + { + emit done(); + return; + } + + if (idsRemoved) + { + mCommandDispatcher->setSelection(mSelectedIds); + } +} diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp index 590cabab1..2e06e9c9c 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.hpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -21,6 +21,7 @@ namespace CSMDoc namespace CSMWorld { class CommandDispatcher; + class Data; } namespace CSVWorld @@ -44,6 +45,8 @@ namespace CSVWorld Mode mMode; CSMWorld::CommandDispatcher *mCommandDispatcher; + CSMWorld::Data &mData; + std::vector mSelectedIds; void setupGroupLayout(); void setupCheckBoxes(const std::vector &types); @@ -61,6 +64,7 @@ namespace CSVWorld private slots: void performExtendedCommand(); void checkBoxStateChanged(int state); + void dataIdListChanged(); signals: void done(); From 9a936046f6bf5d56825a59b9f7c94076c297b889 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 5 Jul 2015 22:49:48 +0300 Subject: [PATCH 123/441] Add edit locking to ExtendedCommandConfigurator --- .../world/extendedcommandconfigurator.cpp | 26 ++++++++++++++++++- .../world/extendedcommandconfigurator.hpp | 4 +++ apps/opencs/view/world/tablebottombox.cpp | 1 + 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index d243ff256..2cf6222a6 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -20,7 +20,8 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum mNumUsedCheckBoxes(0), mNumChecked(0), mMode(Mode_None), - mData(document.getData()) + mData(document.getData()), + mEditLock(false) { mCommandDispatcher = new CSMWorld::CommandDispatcher(document, id, this); @@ -58,8 +59,19 @@ void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandC mPerformButton->setText((mMode == Mode_Delete) ? "Extended Delete" : "Extended Revert"); mSelectedIds = selectedIds; mCommandDispatcher->setSelection(mSelectedIds); + setupCheckBoxes(mCommandDispatcher->getExtendedTypes()); setupGroupLayout(); + lockWidgets(mEditLock); + } +} + +void CSVWorld::ExtendedCommandConfigurator::setEditLock(bool locked) +{ + if (mEditLock != locked) + { + mEditLock = locked; + lockWidgets(mEditLock); } } @@ -144,6 +156,18 @@ void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vectorsetEnabled(!mEditLock && mNumChecked > 0); + + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (int i = 0; current != end && i < mNumUsedCheckBoxes; ++current, ++i) + { + current->first->setEnabled(!mEditLock); + } +} + void CSVWorld::ExtendedCommandConfigurator::performExtendedCommand() { std::vector types; diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp index 2e06e9c9c..641b4a524 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.hpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -48,8 +48,11 @@ namespace CSVWorld CSMWorld::Data &mData; std::vector mSelectedIds; + bool mEditLock; + void setupGroupLayout(); void setupCheckBoxes(const std::vector &types); + void lockWidgets(bool locked); public: ExtendedCommandConfigurator(CSMDoc::Document &document, @@ -57,6 +60,7 @@ namespace CSVWorld QWidget *parent = 0); void configure(Mode mode, const std::vector &selectedIds); + void setEditLock(bool locked); protected: virtual void resizeEvent(QResizeEvent *event); diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index c56c392d8..d22bcde4d 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -123,6 +123,7 @@ void CSVWorld::TableBottomBox::setEditLock (bool locked) { if (mCreator) mCreator->setEditLock (locked); + mExtendedConfigurator->setEditLock (locked); } CSVWorld::TableBottomBox::~TableBottomBox() From f4776112c606fb783af3b97b2d5c93577c6203d9 Mon Sep 17 00:00:00 2001 From: Arthur Moore Date: Tue, 21 Jul 2015 22:08:37 -0400 Subject: [PATCH 124/441] (Re) Wrote a tool to test NIF files in BSAs and on the filesystem. Just give it a set of files, one file per argument, and it will make sure openmw can read them. --- CMakeLists.txt | 7 +++ apps/niftest/CMakeLists.txt | 19 ++++++++ apps/niftest/find_bad_nifs.sh | 28 +++++++++++ apps/niftest/niftest.cpp | 87 +++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 apps/niftest/CMakeLists.txt create mode 100755 apps/niftest/find_bad_nifs.sh create mode 100644 apps/niftest/niftest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f3a0fcc1..d6705ad8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -421,6 +421,9 @@ IF(NOT WIN32 AND NOT APPLE) IF(BUILD_ESMTOOL) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_ESMTOOL) + IF(BUILD_NIFTEST) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_NIFTEST) IF(BUILD_MWINIIMPORTER) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-iniimporter" DESTINATION "${BINDIR}" ) ENDIF(BUILD_MWINIIMPORTER) @@ -617,6 +620,10 @@ if (BUILD_WIZARD) add_subdirectory(apps/wizard) endif() +if (BUILD_NIFTEST) + add_subdirectory(apps/niftest) +endif(BUILD_NIFTEST) + # UnitTests if (BUILD_UNITTESTS) add_subdirectory( apps/openmw_test_suite ) diff --git a/apps/niftest/CMakeLists.txt b/apps/niftest/CMakeLists.txt new file mode 100644 index 000000000..d7f0200d2 --- /dev/null +++ b/apps/niftest/CMakeLists.txt @@ -0,0 +1,19 @@ +set(NIFTEST + niftest.cpp +) +source_group(components\\nif\\tests FILES ${NIFTEST}) + +# Main executable +add_executable(niftest + ${NIFTEST} +) + +target_link_libraries(niftest + ${Boost_FILESYSTEM_LIBRARY} + components +) + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions (--coverage) + target_link_libraries(niftest gcov) +endif() diff --git a/apps/niftest/find_bad_nifs.sh b/apps/niftest/find_bad_nifs.sh new file mode 100755 index 000000000..4b599f442 --- /dev/null +++ b/apps/niftest/find_bad_nifs.sh @@ -0,0 +1,28 @@ +#!/bin/bash +#Script to test all nif files (both loose, and in BSA archives) in data files directory + +#The user input as an absolute path +DATAFILESDIR="`readlink -m "$1"`" +#Program used to test +TEST_PROG="`pwd`/niftest" + +#Make sure our files don't bother anyone +NIFTEST_TMP_DIR="/tmp/niftest_$RANDOM/" +mkdir "$NIFTEST_TMP_DIR" +cd "$NIFTEST_TMP_DIR" + +find "$DATAFILESDIR" -iname *bsa > nifs.txt +find "$DATAFILESDIR" -iname *nif >> nifs.txt + +sed -e 's/.*/\"&\"/' nifs.txt > quoted_nifs.txt + +xargs --arg-file=quoted_nifs.txt "$TEST_PROG" 2>&1 | tee nif_out.txt +# xargs --arg-file=quoted_nifs.txt "$TEST_PROG" 2> nif_out.txt >/dev/null + +echo "List of bad NIF Files:" +cat nif_out.txt|grep File:|cut -d ' ' -f 2- + +rm nifs.txt +rm quoted_nifs.txt +rm nif_out.txt +rmdir "$NIFTEST_TMP_DIR" diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp new file mode 100644 index 000000000..20c8597f3 --- /dev/null +++ b/apps/niftest/niftest.cpp @@ -0,0 +1,87 @@ +///Program to test .nif files both on the FileSystem and in BSA archives. + +#include +#include + +#include +#include +#include +#include + + +///See if the file has the named extension +bool hasExtension(std::string filename, std::string extensionToFind) +{ + std::string extension = filename.substr(filename.find_last_of(".")+1); + + //Convert strings to lower case for comparison + std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower); + + if(extension == extensionToFind) + return true; + else + return false; +} + +///See if the file has the "nif" extension. +bool isNIF(std::string filename) +{ + return hasExtension(filename,"nif"); +} +///See if the file has the "bsa" extension. +bool isBSA(std::string filename) +{ + return hasExtension(filename,"bsa"); +} + +///Check all the nif files in the given BSA archive +void readBSA(std::string filename) +{ + VFS::Manager myManager(false); + myManager.addArchive(new VFS::BsaArchive(filename)); + myManager.buildIndex(); + + std::map files=myManager.getIndex(); + for(std::map::const_iterator it=files.begin(); it!=files.end(); ++it) + { + std::string name = it->first; + if(isNIF(name)) + { +// std::cout << "Decoding: " << name << std::endl; + Nif::NIFFile temp_nif(myManager.get(name),name); + } + } +} + +int main(int argc, char **argv) +{ + + std::cout << "Reading Files" << std::endl; + for(int i = 1; i Date: Wed, 29 Jul 2015 14:45:56 +0200 Subject: [PATCH 125/441] allow keywords in quotes (Fixes #2794) --- components/compiler/scanner.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 83d435962..de7f7e1e1 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -281,8 +281,10 @@ namespace Compiler if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') { name = name.substr (1, name.size()-2); - cont = parser.parseName (name, loc, *this); - return true; +// allow keywords enclosed in "" +/// \todo optionally disable +// cont = parser.parseName (name, loc, *this); +// return true; } int i = 0; From 7e7c028530836660fd3e307b8d70c91a623a8784 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 30 Jul 2015 10:41:18 +1000 Subject: [PATCH 126/441] Fix formatting issues after cherry-picking commit ab0b5932f76ff57b79e894567d9a2d9bde8ffd12 --- apps/opencs/view/world/dialoguesubview.cpp | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 27b35c17e..48485c5aa 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -325,11 +325,11 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di Q_ASSERT(mWidget != NULL); Q_ASSERT(CSMWorld::ColumnBase::isId(display)); Q_ASSERT(mIdType != CSMWorld::UniversalId::Type_None); - + mWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(mWidget, - SIGNAL(customContextMenuRequested(const QPoint &)), - this, + connect(mWidget, + SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(showContextMenu(const QPoint &))); mEditIdAction = new QAction(this); @@ -353,7 +353,7 @@ void CSVWorld::IdContextMenu::excludeId(const std::string &id) QString CSVWorld::IdContextMenu::getWidgetValue() const { - QLineEdit *lineEdit = qobject_cast(mWidget); + QLineEdit *lineEdit = qobject_cast(mWidget); QLabel *label = qobject_cast(mWidget); QString value = ""; @@ -412,7 +412,7 @@ void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) { removeEditIdActionFromMenu(); } - + if (!mContextMenu->actions().isEmpty()) { mContextMenu->exec(mWidget->mapToGlobal(pos)); @@ -591,9 +591,9 @@ void CSVWorld::EditWidget::remake(int row) tablesLayout->addWidget(label); tablesLayout->addWidget(table); - connect(table, - SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), - this, + connect(table, + SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), + this, SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); } else if (!(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) @@ -906,7 +906,7 @@ void CSVWorld::DialogueSubView::setEditLock (bool locked) SimpleDialogueSubView::setEditLock (locked); if (mButtons) - mButtons->setEditLock (locked); + mButtons->setEditLock (locked); } void CSVWorld::DialogueSubView::updateUserSetting (const QString& name, const QStringList& value) @@ -931,7 +931,7 @@ void CSVWorld::DialogueSubView::updateUserSetting (const QString& name, const QS } if (mButtons) - mButtons->updateUserSetting (name, value); + mButtons->updateUserSetting (name, value); } void CSVWorld::DialogueSubView::showPreview () @@ -986,5 +986,5 @@ void CSVWorld::DialogueSubView::requestFocus (const std::string& id) QModelIndex index = getTable().getModelIndex (id, 0); if (index.isValid()) - switchToRow (index.row()); + switchToRow (index.row()); } From 7247da5a770ed05a014db381bc011f398891beb0 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 30 Jul 2015 15:48:45 +1000 Subject: [PATCH 127/441] Fix loading openmw-template.omwgame with debug builds (see: https://forum.openmw.org/viewtopic.php?f=7&p=34026#p34026). Script::load() was failing with debug assertion fail (vector subscript out of range) --- components/esm/loadscpt.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 0c2bdd42f..60b4a3304 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -77,8 +77,13 @@ namespace ESM break; case ESM::FourCC<'S','C','D','T'>::value: // compiled script - mScriptData.resize(mData.mScriptDataSize); - esm.getHExact(&mScriptData[0], mScriptData.size()); + if (mData.mScriptDataSize) + { + mScriptData.resize(mData.mScriptDataSize); + esm.getHExact(&mScriptData[0], mScriptData.size()); + } + else + esm.skipHSub(); break; case ESM::FourCC<'S','C','T','X'>::value: mScriptText = esm.getHString(); From 5bef43ac14cb1e820d99335d4fcb8d8bb196b80a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 30 Jul 2015 16:30:59 +1000 Subject: [PATCH 128/441] Remove duplicated config scanning (see: https://forum.openmw.org/viewtopic.php?f=7&t=2922&p=32940#p32940) * Requires boost::filesystem::canonical() from v1.48 * reduces startup time * Fixes asset files being listed multiple times in tables --- apps/opencs/editor.cpp | 10 +++++++++- components/files/configurationmanager.cpp | 18 ++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 0bcaff6a5..c70b3dd19 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -159,15 +159,23 @@ std::pair > CS::Editor::readConfi } dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); + Files::PathContainer canonicalPaths; //iterate the data directories and add them to the file dialog for loading for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) { + boost::filesystem::path p = boost::filesystem::canonical(*iter); + Files::PathContainer::iterator it = std::find(canonicalPaths.begin(), canonicalPaths.end(), p); + if (it == canonicalPaths.end()) + canonicalPaths.push_back(p); + else + continue; + QString path = QString::fromUtf8 (iter->string().c_str()); mFileDialog.addFiles(path); } - return std::make_pair (dataDirs, variables["fallback-archive"].as >()); + return std::make_pair (canonicalPaths, variables["fallback-archive"].as >()); } void CS::Editor::createGame() diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index dc6f02b60..ac461697a 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -57,14 +57,24 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m bool silent = mSilent; mSilent = quiet; + boost::filesystem::path pUser = boost::filesystem::canonical(mFixedPath.getUserConfigPath()); + boost::filesystem::path pLocal = boost::filesystem::canonical(mFixedPath.getLocalPath()); + boost::filesystem::path pGlobal = boost::filesystem::canonical(mFixedPath.getGlobalConfigPath()); + loadConfig(mFixedPath.getUserConfigPath(), variables, description); boost::program_options::notify(variables); - loadConfig(mFixedPath.getLocalPath(), variables, description); - boost::program_options::notify(variables); - loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); - boost::program_options::notify(variables); + if (pLocal != pUser) + { + loadConfig(mFixedPath.getLocalPath(), variables, description); + boost::program_options::notify(variables); + } + if (pGlobal != pUser && pGlobal != pLocal) + { + loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); + boost::program_options::notify(variables); + } mSilent = silent; } From 870bb491af6ddf01e8706d6c0dafd64c03659a7a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 31 Jul 2015 09:05:26 +1000 Subject: [PATCH 129/441] Fix for issue #6 (https://github.com/cc9cii/openmw/issues/6) where dialogue subview for editing an NPC fails with an "invalid ID" exception. * NPC autocalc code was looking for non-existent values of race and class, this is now validated first. * Also took the opportunity to grey out the spells table when auto-calculated. The template specialisation is a bit ugly, though. --- apps/opencs/model/world/data.cpp | 14 ++- apps/opencs/model/world/refidadapterimp.cpp | 126 +++++++++++++++++++- apps/opencs/model/world/refidadapterimp.hpp | 43 ------- apps/opencs/view/world/nestedtable.cpp | 31 +++-- 4 files changed, 145 insertions(+), 69 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index c7e676361..648761b11 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -552,7 +552,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mMetaData.addColumn (new FormatColumn); mMetaData.addColumn (new AuthorColumn); mMetaData.addColumn (new FileDescriptionColumn); - + addModel (new IdTable (&mGlobals), UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst); addModel (new IdTable (&mSkills), UniversalId::Type_Skill); @@ -968,7 +968,7 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mMetaData.setRecord (0, Record (RecordBase::State_ModifiedOnly, 0, &metaData)); } - + return mReader->getRecordCount(); } @@ -1419,8 +1419,14 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const if (cachedStats) return cachedStats; - const ESM::Race *race = &mRaces.getRecord(npc.mRace).get(); - const ESM::Class *class_ = &mClasses.getRecord(npc.mClass).get(); + int raceIndex = mRaces.searchId(npc.mRace); + int classIndex = mClasses.searchId(npc.mClass); + // this can happen when creating a new game from scratch + if (raceIndex == -1 || classIndex == -1) + return 0; + + const ESM::Race *race = &mRaces.getRecord(raceIndex).get(); + const ESM::Class *class_ = &mClasses.getRecord(classIndex).get(); bool autoCalc = npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; short level = npc.mNpdt52.mLevel; diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 2bb980bfe..16579c2c7 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -638,6 +638,11 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + if (!stats) + { + record.setModified (npc); + return; + } // update npc npc.mNpdtType = ESM::NPC::NPC_DEFAULT; @@ -755,6 +760,8 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + if (!stats) + return QVariant(); switch (subRowIndex) { @@ -885,6 +892,9 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + if (!stats) + return QVariant(); + return static_cast(stats->getBaseSkill(subRowIndex)); } else @@ -981,17 +991,23 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column } case 2: { - UserInt i(stats->getHealth()); + UserInt i(0); + if (stats) + i = UserInt(stats->getHealth()); return QVariant(QVariant::fromValue(i)); } case 3: { - UserInt i(stats->getMana()); + UserInt i(0); + if (stats) + i = UserInt(stats->getMana()); return QVariant(QVariant::fromValue(i)); } case 4: { - UserInt i(stats->getFatigue()); + UserInt i(0); + if (stats) + i = UserInt(stats->getFatigue()); return QVariant(QVariant::fromValue(i)); } case 5: return static_cast(record.get().mNpdt12.mDisposition); @@ -1164,6 +1180,54 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData namespace CSMWorld { +template<> +QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, + int index) const +{ + const Record& record = static_cast&> ( + data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + + if (column==mActors.mHasAi) + return record.get().mHasAI!=0; + + if (column==mActors.mHello) + return record.get().mAiData.mHello; + + if (column==mActors.mFlee) + return record.get().mAiData.mFlee; + + if (column==mActors.mFight) + return record.get().mAiData.mFight; + + if (column==mActors.mAlarm) + return record.get().mAiData.mAlarm; + + if (column==mActors.mInventory) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + if (column==mActors.mSpells) + { + if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) + return QVariant(QVariant::UserType); + else + return true; + } + + if (column==mActors.mDestinations) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + if (column==mActors.mAiPackages) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + std::map::const_iterator iter = + mActors.mServices.find (column); + + if (iter!=mActors.mServices.end()) + return (record.get().mAiData.mServices & iter->second)!=0; + + return NameRefIdAdapter::getData (column, data, index); +} + template <> void NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const @@ -1259,7 +1323,11 @@ QVariant NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *co const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - const std::vector& spells = mData.npcAutoCalculate(record.get())->spells(); + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(record.get()); + if (!stats) + return QVariant(); + + const std::vector& spells = stats->spells(); if (subRowIndex < 0 || subRowIndex >= static_cast (spells.size())) throw std::runtime_error ("index out of range"); @@ -1289,8 +1357,54 @@ int NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *col const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - const std::vector spells = mData.npcAutoCalculate(record.get())->spells(); - return static_cast(spells.size()); + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(record.get()); + if (!stats) + return 0; + + return static_cast(stats->spells().size()); +} + +template<> +QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, + int index) const +{ + const Record& record = static_cast&> ( + data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + + if (column==mActors.mHasAi) + return record.get().mHasAI!=0; + + if (column==mActors.mHello) + return record.get().mAiData.mHello; + + if (column==mActors.mFlee) + return record.get().mAiData.mFlee; + + if (column==mActors.mFight) + return record.get().mAiData.mFight; + + if (column==mActors.mAlarm) + return record.get().mAiData.mAlarm; + + if (column==mActors.mInventory) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + if (column==mActors.mSpells) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + if (column==mActors.mDestinations) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + if (column==mActors.mAiPackages) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + std::map::const_iterator iter = + mActors.mServices.find (column); + + if (iter!=mActors.mServices.end()) + return (record.get().mAiData.mServices & iter->second)!=0; + + return NameRefIdAdapter::getData (column, data, index); } template <> diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 4a8288fa4..9fc296231 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -523,49 +523,6 @@ namespace CSMWorld : NameRefIdAdapter (type, columns), mActors (columns) {} - template - QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const - { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - - if (column==mActors.mHasAi) - return record.get().mHasAI!=0; - - if (column==mActors.mHello) - return record.get().mAiData.mHello; - - if (column==mActors.mFlee) - return record.get().mAiData.mFlee; - - if (column==mActors.mFight) - return record.get().mAiData.mFight; - - if (column==mActors.mAlarm) - return record.get().mAiData.mAlarm; - - if (column==mActors.mInventory) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - if (column==mActors.mSpells) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - if (column==mActors.mDestinations) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - if (column==mActors.mAiPackages) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - std::map::const_iterator iter = - mActors.mServices.find (column); - - if (iter!=mActors.mServices.end()) - return (record.get().mAiData.mServices & iter->second)!=0; - - return NameRefIdAdapter::getData (column, data, index); - } - template void ActorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index de3b3aa16..89b171891 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -22,6 +22,8 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, mEditIdAction(0), mModel(model) { + mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); + setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); @@ -34,26 +36,23 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, int columns = model->columnCount(QModelIndex()); - setModel(model); - - setAcceptDrops(true); - - if (editable) + for(int i = 0 ; i < columns; ++i) { - mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); - for(int i = 0 ; i < columns; ++i) - { - CSMWorld::ColumnBase::Display display = static_cast ( - model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + CSMWorld::ColumnBase::Display display = static_cast ( + model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display, - mDispatcher, - document, - this); + CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display, + mDispatcher, + document, + this); - setItemDelegateForColumn(i, delegate); - } + setItemDelegateForColumn(i, delegate); + } + + setModel(model); + if (editable) + { mAddNewRowAction = new QAction (tr ("Add new row"), this); connect(mAddNewRowAction, SIGNAL(triggered()), From 5538f822f468fb8d421123037ff4762a23344604 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 1 Aug 2015 07:50:56 +1000 Subject: [PATCH 130/441] Initial support for Land and LandTexture tables. Updates Features #936 and #933. --- apps/opencs/model/world/data.cpp | 10 ++++++++++ apps/opencs/model/world/universalid.cpp | 8 ++++++-- apps/opencs/model/world/universalid.hpp | 4 ++++ apps/opencs/view/doc/view.cpp | 18 ++++++++++++++++++ apps/opencs/view/doc/view.hpp | 4 ++++ apps/opencs/view/world/subviews.cpp | 4 +++- 6 files changed, 45 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 648761b11..7b3e1fe4f 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -553,6 +553,14 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mMetaData.addColumn (new AuthorColumn); mMetaData.addColumn (new FileDescriptionColumn); + mLandTextures.addColumn (new StringIdColumn); + mLandTextures.addColumn (new RecordStateColumn); + mLandTextures.addColumn (new FixedRecordTypeColumn (UniversalId::Type_LandTexture)); + + mLand.addColumn (new StringIdColumn); + mLand.addColumn (new RecordStateColumn); + mLand.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Land)); + addModel (new IdTable (&mGlobals), UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst); addModel (new IdTable (&mSkills), UniversalId::Type_Skill); @@ -594,6 +602,8 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Videos)), UniversalId::Type_Video); addModel (new IdTable (&mMetaData), UniversalId::Type_MetaData); + addModel (new IdTable (&mLand), UniversalId::Type_Land); + addModel (new IdTable (&mLandTextures), UniversalId::Type_LandTexture); // for autocalc updates when gmst/race/class/skils tables change CSMWorld::IdTable *gmsts = diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 73d893a26..8ad9873fc 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -57,6 +57,8 @@ namespace { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Meta Data Table", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "Land Texture Table", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Land Table", 0 }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; @@ -122,6 +124,8 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Meta Data", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "Land Texture", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", 0 }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; @@ -365,8 +369,8 @@ std::vector CSMWorld::UniversalId::listTypes (int c for (int i=0; sIndexArg[i].mName; ++i) if (sIndexArg[i].mClass & classes) list.push_back (sIndexArg[i].mType); - - return list; + + return list; } CSMWorld::UniversalId::Type CSMWorld::UniversalId::getParentType (Type type) diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index e9104fc22..a05843597 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -133,6 +133,10 @@ namespace CSMWorld Type_Search, Type_MetaDatas, Type_MetaData, + Type_LandTextures, + Type_LandTexture, + Type_Lands, + Type_Land, Type_RunLog }; diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 78203666d..173a78e9d 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -169,6 +169,10 @@ void CSVDoc::View::setupWorldMenu() connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView())); world->addAction (grid); + QAction *land = new QAction (tr ("Lands"), this); + connect (land, SIGNAL (triggered()), this, SLOT (addLandSubView())); + world->addAction (land); + world->addSeparator(); // items that don't represent single record lists follow here QAction *regionMap = new QAction (tr ("Region Map"), this); @@ -288,6 +292,10 @@ void CSVDoc::View::setupAssetsMenu() connect (textures, SIGNAL (triggered()), this, SLOT (addTexturesSubView())); assets->addAction (textures); + QAction *land = new QAction (tr ("Land Textures"), this); + connect (land, SIGNAL (triggered()), this, SLOT (addLandTextureSubView())); + assets->addAction (land); + QAction *videos = new QAction (tr ("Videos"), this); connect (videos, SIGNAL (triggered()), this, SLOT (addVideosSubView())); assets->addAction (videos); @@ -838,6 +846,16 @@ void CSVDoc::View::addPathgridSubView() addSubView (CSMWorld::UniversalId::Type_Pathgrids); } +void CSVDoc::View::addLandTextureSubView() +{ + addSubView (CSMWorld::UniversalId::Type_LandTextures); +} + +void CSVDoc::View::addLandSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Lands); +} + void CSVDoc::View::addStartScriptsSubView() { addSubView (CSMWorld::UniversalId::Type_StartScripts); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index dc8e14fb5..7f465c8f3 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -228,6 +228,10 @@ namespace CSVDoc void addPathgridSubView(); + void addLandTextureSubView(); + + void addLandSubView(); + void addStartScriptsSubView(); void addSearchSubView(); diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index b8a6ba429..73000af13 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -43,6 +43,8 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_BodyParts, CSMWorld::UniversalId::Type_SoundGens, CSMWorld::UniversalId::Type_Pathgrids, + CSMWorld::UniversalId::Type_LandTextures, + CSMWorld::UniversalId::Type_Lands, CSMWorld::UniversalId::Type_StartScripts, CSMWorld::UniversalId::Type_None // end marker @@ -172,7 +174,7 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_MetaData, new CSVDoc::SubViewFactory); - + //preview manager.add (CSMWorld::UniversalId::Type_Preview, new CSVDoc::SubViewFactory); } From da85b3a4ee3125acd2e8a09012561d84cd246cd1 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 1 Aug 2015 13:25:35 +1000 Subject: [PATCH 131/441] Update to keeping config comments (Feature #2535) -An attempt to fix Coverity-Scan warning (see: https://github.com/OpenMW/openmw/pull/618#discussion_r33641670) - Unable to test until a PR can be submitted. --- components/config/gamesettings.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index ca6bfd80d..10b0234d1 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -291,7 +291,8 @@ bool Config::GameSettings::writeFileWithComments(QFile &file) if (!comments.empty() && index != -1 && settingRegex.captureCount() >= 2 && mUserSettings.find(settingRegex.cap(1)) != mUserSettings.end()) { - for (std::vector::const_iterator it = comments.begin(); it != comments.end(); ++it) + for (std::vector::const_iterator it = comments.begin(); + it != comments.end() && commentStart != fileCopy.end(); ++it) { *commentStart = *it; ++commentStart; From b920e1bde7c2333386eefc8aa2c72d13cc4b2b8e Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 12 Aug 2015 06:50:22 +1000 Subject: [PATCH 132/441] Support MSVC 2015. Tested(*) with updated dependencies: - Microsoft Visual Studio Community 2015 Version 14.0.23107.0 D14REL - Qt 5.5, commit 1d3966833b5f27fb262f2d3727557ef2c8947828 - SDL2 default branch, Changeset: 9834 (d3fa6d0d3793) - Ogre default branch, Changeset: 8048 (19479be2c7c5) - Boost 1.59, commit 9a02cf8eb34eb31f0858c223ce95319f103addfa - OpenAL commit 8fa4f276f89985be44007ce166109837cbfd5763 (*) only tested compilation and startup of the each app --- apps/bsatool/bsatool.cpp | 1 + apps/essimporter/importer.cpp | 4 ++++ apps/opencs/view/render/mousestate.cpp | 2 ++ apps/opencs/view/render/overlaymask.cpp | 1 + apps/opencs/view/render/pagedworldspacewidget.cpp | 2 ++ apps/opencs/view/render/textoverlay.cpp | 2 ++ apps/opencs/view/world/physicssystem.cpp | 2 ++ apps/openmw/engine.cpp | 1 + apps/openmw/mwworld/worldimp.cpp | 10 +++++++--- components/bsa/bsa_archive.cpp | 12 ++++++------ components/files/configurationmanager.hpp | 6 +++++- components/settings/settings.cpp | 1 + components/terrain/terraingrid.cpp | 1 + libs/platform/strings.h | 4 +++- 14 files changed, 38 insertions(+), 11 deletions(-) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index c0a6dcc81..d87abc285 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index d5ed43b8a..35b92a4d0 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -1,7 +1,11 @@ #include "importer.hpp" + +#include + #include #include +#include #include #include diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index 206820194..8aecc4a41 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include diff --git a/apps/opencs/view/render/overlaymask.cpp b/apps/opencs/view/render/overlaymask.cpp index 09f020354..b43abb3cc 100644 --- a/apps/opencs/view/render/overlaymask.cpp +++ b/apps/opencs/view/render/overlaymask.cpp @@ -1,5 +1,6 @@ #include "overlaymask.hpp" +#include #include #include diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index cf9edb548..21fba4d4e 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include "textoverlay.hpp" diff --git a/apps/opencs/view/render/textoverlay.cpp b/apps/opencs/view/render/textoverlay.cpp index c41d5f318..14b60be93 100644 --- a/apps/opencs/view/render/textoverlay.cpp +++ b/apps/opencs/view/render/textoverlay.cpp @@ -5,9 +5,11 @@ #include #include +#include #include #include #include +#include #include #include #include diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index 2cbe17dcf..891b175d5 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 4496490d4..9d40ebb1b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f8de9f65b..a1aa0166d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,11 +1,15 @@ #include "worldimp.hpp" #if defined(_WIN32) && !defined(__MINGW32__) -#include +# if (_MSC_VER < 1900) +# include +# else +# include +# endif #elif defined HAVE_UNORDERED_MAP -#include +# include #else -#include +# include #endif #include "../mwbase/scriptmanager.hpp" #include "../mwscript/globalscripts.hpp" diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 4f656f9c4..094b058fd 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -104,7 +104,7 @@ public: void load() {} void unload() {} - DataStreamPtr open(const String& filename, bool readonly = true) const + virtual DataStreamPtr open(const String& filename, bool readonly = true) { index::const_iterator i = lookup_filename (filename); @@ -149,8 +149,8 @@ public: time_t getModifiedTime(const String&) { return 0; } - FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, - bool dirs = false) const + virtual FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, + bool dirs = false) { std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); @@ -216,7 +216,7 @@ public: void load() {} void unload() {} - DataStreamPtr open(const String& filename, bool readonly = true) const + virtual DataStreamPtr open(const String& filename, bool readonly = true) { // Get a non-const reference to arc. This is a hack and it's all // OGRE's fault. You should NOT expect an open() command not to @@ -262,8 +262,8 @@ public: return ptr; } - FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, - bool dirs = false) const + virtual FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, + bool dirs = false) { std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 5f0062c2e..07b95f25a 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -2,7 +2,11 @@ #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #if defined(_WIN32) && !defined(__MINGW32__) -#include +#if (_MSC_VER >= 1900) +#include +#else +#include +#endif #elif defined HAVE_UNORDERED_MAP #include #else diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index a9a78d035..7827388b1 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -2,6 +2,7 @@ #include +#include // FIXME: workaround compilation error with OgreCommon.h included by OgreStringConverter.h #include #include diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index bb99ca23e..feea61993 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "chunk.hpp" diff --git a/libs/platform/strings.h b/libs/platform/strings.h index c0fbb1a1b..305705044 100644 --- a/libs/platform/strings.h +++ b/libs/platform/strings.h @@ -9,7 +9,9 @@ #elif defined(MSVC) || defined(_MSC_VER) # pragma warning(disable: 4996) # define strcasecmp stricmp -# define snprintf _snprintf +# if (_MSC_VER < 1900) +# define snprintf _snprintf +# endif #else # warning "Unable to determine your compiler, you should probably take a look here." # include // Just take a guess From bb250a236b32d0e8eab585e48f408deeea4f6121 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 13 Aug 2015 13:09:40 +1000 Subject: [PATCH 133/441] Fix compilation when using 1.9 Ogre branch (i.e. the previous change on this file was in fact not related to MSVC 2015, but rather the use of the Ogre default branch). --- components/bsa/bsa_archive.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 094b058fd..35fbf62a8 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -30,6 +30,17 @@ #include #include #include +/* + * This test for ogre version is not realy correct, because the change happened since + * commit d5e05e9d97f47bce40aa41a2bf31c2b6c3fde5f3 (2014-02-24) on the default branch + * rather than during an ogre version change event. However it should be good enough. + */ +#if OGRE_VERSION < 0x010a00 +#define OGRE_CONST const +#else +#define OGRE_CONST +#endif + #include "bsa_file.hpp" #include "../files/constrainedfiledatastream.hpp" @@ -104,7 +115,7 @@ public: void load() {} void unload() {} - virtual DataStreamPtr open(const String& filename, bool readonly = true) + virtual DataStreamPtr open(const String& filename, bool readonly = true) OGRE_CONST { index::const_iterator i = lookup_filename (filename); @@ -150,7 +161,7 @@ public: time_t getModifiedTime(const String&) { return 0; } virtual FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, - bool dirs = false) + bool dirs = false) OGRE_CONST { std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); @@ -216,7 +227,7 @@ public: void load() {} void unload() {} - virtual DataStreamPtr open(const String& filename, bool readonly = true) + virtual DataStreamPtr open(const String& filename, bool readonly = true) OGRE_CONST { // Get a non-const reference to arc. This is a hack and it's all // OGRE's fault. You should NOT expect an open() command not to @@ -263,7 +274,7 @@ public: } virtual FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, - bool dirs = false) + bool dirs = false) OGRE_CONST { std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); From 2242920b85c1cf9b1794961236bbc38af01fccb1 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 13 Aug 2015 13:11:17 +1000 Subject: [PATCH 134/441] Fix compiling with MSVC 2013 (typo mistake with the include file). --- components/files/configurationmanager.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 07b95f25a..63ae3c6a4 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -5,7 +5,7 @@ #if (_MSC_VER >= 1900) #include #else -#include +#include #endif #elif defined HAVE_UNORDERED_MAP #include From ec98a1220f3907de02f3dd0fde0495f21517e3de Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 2 Aug 2015 15:03:27 +0300 Subject: [PATCH 135/441] Proper index for Modified column in ModifyCommand --- apps/opencs/model/world/commands.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 5e0cc8f88..019ffe7b2 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -33,7 +33,14 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI { mHasRecordState = true; int stateColumnIndex = table->findColumnIndex(Columns::ColumnId_Modification); - mRecordStateIndex = table->index(mIndex.row(), stateColumnIndex); + + int rowIndex = mIndex.row(); + if (mIndex.parent().isValid()) + { + rowIndex = mIndex.parent().row(); + } + + mRecordStateIndex = table->index(rowIndex, stateColumnIndex); mOldRecordState = static_cast(table->data(mRecordStateIndex).toInt()); } } From 4366bdb4a883696e83b24900ea66a8716c2242e7 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 2 Aug 2015 15:06:56 +0300 Subject: [PATCH 136/441] Inform about Modified status change when modifying a value of a model --- apps/opencs/model/world/idtable.cpp | 6 +++++- apps/opencs/model/world/idtree.cpp | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 8ca19f7e9..bdbc5e0c4 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -77,7 +77,11 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value { mIdCollection->setData (index.row(), index.column(), value); - emit dataChanged (index, index); + // Modifying a value can also change the Modified status of a record. + // To track this, we inform about the change of a whole row. + QModelIndex rowStart = this->index(index.row(), 0); + QModelIndex rowEnd = this->index(index.row(), columnCount(index.parent()) - 1); + emit dataChanged(rowStart, rowEnd); return true; } diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index d8e8c6107..42ce72514 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -96,7 +96,13 @@ bool CSMWorld::IdTree::setData (const QModelIndex &index, const QVariant &value, mNestedCollection->setNestedData(parentAddress.first, parentAddress.second, value, index.row(), index.column()); - emit dataChanged (index, index); + emit dataChanged(index, index); + + // Modifying a value can also change the Modified status of a record (located in the parent row). + // To track this, we inform about the change of a whole parent row. + QModelIndex parentRowStart = this->index(index.parent().row(), 0); + QModelIndex parentRowEnd = this->index(index.parent().row(), columnCount(index.parent()) - 1); + emit dataChanged(parentRowStart, parentRowEnd); return true; } From bba3d6bec5ec217e067ed013dbdd32e1da65cdac Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 2 Aug 2015 17:16:06 +0300 Subject: [PATCH 137/441] Restore Modified status of a record when adding/removing nested rows --- apps/opencs/model/world/columnbase.hpp | 6 ++++++ apps/opencs/model/world/commands.cpp | 22 ++++++++++++++-------- apps/opencs/model/world/commands.hpp | 6 ++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 59f2836c2..c4789ed22 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -192,6 +192,12 @@ namespace CSMWorld ColumnBase::Display_NestedHeader, flags) {} + virtual void set (Record& record, const QVariant& data) + { + // There is nothing to do here. + // This prevents exceptions from parent's implementation + } + virtual QVariant get (const Record& record) const { return true; // required by IdTree::hasChildren() diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 019ffe7b2..7673e6bc9 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -289,21 +289,24 @@ CSMWorld::DeleteNestedCommand::DeleteNestedCommand (IdTree& model, std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText (("Delete row in " + title + " sub-table of " + mId).c_str()); + + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); + mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); } void CSMWorld::DeleteNestedCommand::redo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.removeRows (mNestedRow, 1, parentIndex); + mModifyParentCommand->redo(); } void CSMWorld::DeleteNestedCommand::undo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); + mModifyParentCommand->undo(); } CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) @@ -317,20 +320,23 @@ CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& i std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText (("Add row in " + title + " sub-table of " + mId).c_str()); + + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); + mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); } void CSMWorld::AddNestedCommand::redo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.addNestedRow (parentIndex, mNewRow); + mModifyParentCommand->redo(); } void CSMWorld::AddNestedCommand::undo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); + mModifyParentCommand->undo(); } CSMWorld::NestedTableStoring::NestedTableStoring(const IdTree& model, const std::string& id, int parentColumn) diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 81c40d0ab..23ffccbd7 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -200,6 +200,9 @@ namespace CSMWorld int mNestedRow; + // The command to redo/undo the Modified status of a record + ModifyCommand *mModifyParentCommand; + public: DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); @@ -219,6 +222,9 @@ namespace CSMWorld int mParentColumn; + // The command to redo/undo the Modified status of a record + ModifyCommand *mModifyParentCommand; + public: AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); From 7a927eec79cf4a95b471f50e413c9700f2655abb Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 2 Aug 2015 22:39:41 +0300 Subject: [PATCH 138/441] Close EditWidget when a proper row removed --- apps/opencs/view/world/dialoguesubview.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 48485c5aa..0713a3a16 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -821,8 +821,13 @@ void CSVWorld::SimpleDialogueSubView::dataChanged (const QModelIndex & index) void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), 0)); + + if (!currentIndex.isValid()) + { + return; + } - if (currentIndex.isValid() && currentIndex.row() >= start && currentIndex.row() <= end) + if (currentIndex.parent() == parent && currentIndex.row() >= start && currentIndex.row() <= end) { if(mEditWidget) { From c0d714adb66ccc49eb4a4959f4dbfccc5bf3f2f9 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 2 Aug 2015 22:53:40 +0300 Subject: [PATCH 139/441] Activate editing of nested table cells by a double click Conflicts: apps/opencs/view/world/dialoguesubview.cpp --- apps/opencs/view/world/dialoguesubview.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 0713a3a16..6b40eca43 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -571,8 +571,9 @@ void CSVWorld::EditWidget::remake(int row) table->setStyleSheet("QTableView { color: gray; }"); table->horizontalHeader()->setStyleSheet("QHeaderView { color: gray; }"); } - else - table->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::CurrentChanged); + // Uncomment below two lines to activate editing of nested table cells by a single click + //else + //table->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::CurrentChanged); table->resizeColumnsToContents(); int rows = mTable->rowCount(mTable->index(row, i)); From bc48ed94bcae83f20cdebef9d3868c77710c8c45 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Mon, 3 Aug 2015 19:24:00 +0300 Subject: [PATCH 140/441] Correct comparison of enum values in IdTableProxyModel --- apps/opencs/model/world/idtableproxymodel.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 10fd92b46..757285412 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -5,6 +5,18 @@ #include "idtablebase.hpp" +namespace +{ + std::string getEnumValue(const std::vector &values, int index) + { + if (index < 0 || index >= static_cast(values.size())) + { + return ""; + } + return values[index]; + } +} + void CSMWorld::IdTableProxyModel::updateColumnMap() { Q_ASSERT(mSourceModel != NULL); @@ -93,7 +105,9 @@ bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex &left, const QModel if (valuesIt != mEnumColumnCache.end()) { - return valuesIt->second[left.data().toInt()] < valuesIt->second[right.data().toInt()]; + std::string first = getEnumValue(valuesIt->second, left.data().toInt()); + std::string second = getEnumValue(valuesIt->second, right.data().toInt()); + return first < second; } return QSortFilterProxyModel::lessThan(left, right); } From ce27f0c3f570d8cf7475a777908cfe6c89d874ac Mon Sep 17 00:00:00 2001 From: Alexandre Moine Date: Tue, 4 Aug 2015 13:05:29 +0200 Subject: [PATCH 141/441] Add appdata file for openmw and install it --- CMakeLists.txt | 3 +++ files/openmw.appdata.xml | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 files/openmw.appdata.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index d6705ad8f..fb1ac83cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -364,6 +364,8 @@ configure_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb.txt if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop "${OpenMW_BINARY_DIR}/openmw.desktop") + configure_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml + "${OpenMW_BINARY_DIR}/openmw.appdata.xml") configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.desktop "${OpenMW_BINARY_DIR}/openmw-cs.desktop") endif() @@ -450,6 +452,7 @@ IF(NOT WIN32 AND NOT APPLE) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/appdata" COMPONENT "openmw") IF(BUILD_OPENCS) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs") diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml new file mode 100644 index 000000000..150eea984 --- /dev/null +++ b/files/openmw.appdata.xml @@ -0,0 +1,39 @@ + + + + openmw.desktop + CC0-1.0 + GPL-3.0 and MIT and zlib + OpenMW + Unofficial open source engine re-implementation of the game Morrowind + +

    + OpenMW is a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. +

    +

    + The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). +

    +

    + Pre-existing modifications created for the original Morrowind engine can be hit-and-miss, but a mod created for Morrowind may not necessarily run in OpenMW. +

    +
    + + + + http://wiki.openmw.org/images/b/b2/Openmw_0.11.1_launcher_1.png + The OpenMW launcher + + + http://wiki.openmw.org/images/f/f1/Screenshot_mournhold_plaza_0.35.png + The Mournhold's plaza on OpenMW + + + http://wiki.openmw.org/images/5/5b/Screenshot_Vivec_seen_from_Ebonheart_0.35.png + Vivec seen from Ebonheart on OpenMW + + +nobrakal@gmail.com + https://openmw.org + https://bugs.openmw.org/ + https://openmw.org/faq/ +
    From e9e057de2ed33e7ada6c102d554707237a12b4fe Mon Sep 17 00:00:00 2001 From: Alexandre Moine Date: Tue, 4 Aug 2015 22:50:53 +0200 Subject: [PATCH 142/441] Rewrite of the description --- files/openmw.appdata.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml index 150eea984..1c16cb9a4 100644 --- a/files/openmw.appdata.xml +++ b/files/openmw.appdata.xml @@ -8,13 +8,13 @@ Unofficial open source engine re-implementation of the game Morrowind

    - OpenMW is a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. + OpenMW is a new engine for 2002's Game of the Year, The Elder Scrolls 3: Morrowind.

    - The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). + It aims to be a fully playable (and improved!), open source implementation of the game's engine and functionality (including mods).

    - Pre-existing modifications created for the original Morrowind engine can be hit-and-miss, but a mod created for Morrowind may not necessarily run in OpenMW. + You will still need the original game data to play OpenMW.

    From c157fef2e09350b10bf739a6b87c7cdc49fbbb53 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 5 Aug 2015 17:20:01 +0200 Subject: [PATCH 143/441] allow use of IDs as function arguments, even if the ID matches a keyword (Fixes #2830) --- components/compiler/exprparser.cpp | 5 ++++- components/compiler/lineparser.cpp | 17 +++++++++++++++++ components/compiler/stringparser.cpp | 24 +++++++++++++++++++++++- components/compiler/stringparser.hpp | 4 ++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index dc36b58d8..1818d04df 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -353,7 +353,10 @@ namespace Compiler if (extensions->isInstruction (keyword, argumentType, hasExplicit)) { // pretend this is not a keyword - return parseName (loc.mLiteral, loc, scanner); + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); } } diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index a71672916..4d6e147fc 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -222,6 +222,23 @@ namespace Compiler bool LineParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { + if (mState==MessageState || mState==MessageCommaState) + { + if (const Extensions *extensions = getContext().getExtensions()) + { + std::string argumentType; // ignored + bool hasExplicit = false; // ignored + if (extensions->isInstruction (keyword, argumentType, hasExplicit)) + { + // pretend this is not a keyword + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); + } + } + } + if (mState==SetMemberVarState) { mMemberName = loc.mLiteral; diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index a86c15794..7a2098fb3 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -4,9 +4,12 @@ #include #include +#include + #include "scanner.hpp" #include "generator.hpp" -#include +#include "context.hpp" +#include "extensions.hpp" namespace Compiler { @@ -33,6 +36,25 @@ namespace Compiler return Parser::parseName (name, loc, scanner); } + bool StringParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) + { + if (const Extensions *extensions = getContext().getExtensions()) + { + std::string argumentType; // ignored + bool hasExplicit = false; // ignored + if (extensions->isInstruction (keyword, argumentType, hasExplicit)) + { + // pretend this is not a keyword + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); + } + } + + return Parser::parseKeyword (keyword, loc, scanner); + } + bool StringParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_comma && mState==StartState) diff --git a/components/compiler/stringparser.hpp b/components/compiler/stringparser.hpp index 3859a2496..52469128f 100644 --- a/components/compiler/stringparser.hpp +++ b/components/compiler/stringparser.hpp @@ -32,6 +32,10 @@ namespace Compiler ///< Handle a name token. /// \return fetch another token? + virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner); + ///< Handle a keyword token. + /// \return fetch another token? + virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner); ///< Handle a special character token. /// \return fetch another token? From c3a31d13b170e3dfa2ce0a9e635b898e6b18104a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 6 Aug 2015 08:45:38 +0200 Subject: [PATCH 144/441] make non-editable fields in dialogue sub view selectable (Fixes #2818) --- apps/opencs/view/world/dialoguesubview.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 6b40eca43..5f6f1343d 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -104,7 +104,9 @@ QWidget* CSVWorld::NotEditableSubDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { - return new QLabel(parent); + QLabel *label = new QLabel(parent); + label->setTextInteractionFlags (Qt::TextSelectableByMouse); + return label; } /* @@ -822,7 +824,7 @@ void CSVWorld::SimpleDialogueSubView::dataChanged (const QModelIndex & index) void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), 0)); - + if (!currentIndex.isValid()) { return; From 7dc81c664a672fa1ff38383a01308ad9dcd44e5a Mon Sep 17 00:00:00 2001 From: Arthur Moore Date: Fri, 7 Aug 2015 20:06:33 -0400 Subject: [PATCH 145/441] Added a help message to niftest Now using the boost argument parser. --- apps/niftest/niftest.cpp | 66 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 20c8597f3..fd060447c 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -2,12 +2,17 @@ #include #include +#include #include #include #include #include +#include + +// Create local aliases for brevity +namespace bpo = boost::program_options; ///See if the file has the named extension bool hasExtension(std::string filename, std::string extensionToFind) @@ -54,13 +59,68 @@ void readBSA(std::string filename) } } +std::vector parseOptions (int argc, char** argv) +{ + bpo::options_description desc("Ensure that OpenMW can use the provided NIF and BSA files\n\n" + "Usages:\n" + " niftool \n" + " Scan the file for read errors.\n\n" + "Allowed options"); + desc.add_options() + ("help,h", "print help message.") + ("input-file", bpo::value< std::vector >(), "input file") + ; + + //Default option if none provided + bpo::positional_options_description p; + p.add("input-file", -1); + + bpo::variables_map variables; + try + { + bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv). + options(desc).positional(p).run(); + bpo::store(valid_opts, variables); + } + catch(std::exception &e) + { + std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" + << desc << std::endl; + exit(1); + } + + bpo::notify(variables); + if (variables.count ("help")) + { + std::cout << desc << std::endl; + exit(1); + } + if (variables.count("input-file")) + { + std::vector files = variables["input-file"].as< std::vector >(); + + std::cout << "Input files are:"; + for(std::vector::const_iterator it=files.begin(); it!=files.end(); ++it) + { + std::cout <<" "<< *it; + } + std::cout << std::endl; + return files; + } + + std::cout << "No input files or directories specified!" << std::endl; + std::cout << desc << std::endl; + exit(1); +} + int main(int argc, char **argv) { + std::vector files = parseOptions (argc, argv); std::cout << "Reading Files" << std::endl; - for(int i = 1; i::const_iterator it=files.begin(); it!=files.end(); ++it) + { + std::string name = *it; try{ if(isNIF(name)) From 8cecfe4e1709c37c42714f5fe7f58f1f5a0be4b1 Mon Sep 17 00:00:00 2001 From: Arthur Moore Date: Fri, 7 Aug 2015 20:20:31 -0400 Subject: [PATCH 146/441] Have niftest handle directories as well Note: BSA files within the directory must be passed manually. --- apps/niftest/niftest.cpp | 48 +++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index fd060447c..c138e4551 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -8,11 +8,14 @@ #include #include #include +#include #include +#include // Create local aliases for brevity namespace bpo = boost::program_options; +namespace bfs = boost::filesystem; ///See if the file has the named extension bool hasExtension(std::string filename, std::string extensionToFind) @@ -40,22 +43,30 @@ bool isBSA(std::string filename) return hasExtension(filename,"bsa"); } -///Check all the nif files in the given BSA archive -void readBSA(std::string filename) +/// Check all the nif files in a given VFS::Archive +/// \note Takes ownership! +void readVFS(VFS::Archive* anArchive) { VFS::Manager myManager(false); - myManager.addArchive(new VFS::BsaArchive(filename)); + myManager.addArchive(anArchive); myManager.buildIndex(); std::map files=myManager.getIndex(); for(std::map::const_iterator it=files.begin(); it!=files.end(); ++it) { - std::string name = it->first; - if(isNIF(name)) - { -// std::cout << "Decoding: " << name << std::endl; - Nif::NIFFile temp_nif(myManager.get(name),name); - } + std::string name = it->first; + + try{ + if(isNIF(name)) + { + // std::cout << "Decoding: " << name << std::endl; + Nif::NIFFile temp_nif(myManager.get(name),name); + } + } + catch (std::exception& e) + { + std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + } } } @@ -97,15 +108,7 @@ std::vector parseOptions (int argc, char** argv) } if (variables.count("input-file")) { - std::vector files = variables["input-file"].as< std::vector >(); - - std::cout << "Input files are:"; - for(std::vector::const_iterator it=files.begin(); it!=files.end(); ++it) - { - std::cout <<" "<< *it; - } - std::cout << std::endl; - return files; + return variables["input-file"].as< std::vector >(); } std::cout << "No input files or directories specified!" << std::endl; @@ -131,11 +134,16 @@ int main(int argc, char **argv) else if(isBSA(name)) { std::cout << "Reading BSA File: " << name << std::endl; - readBSA(name); + readVFS(new VFS::BsaArchive(name)); + } + else if(bfs::is_directory(bfs::path(name))) + { + std::cout << "Reading All Files in: " << name << std::endl; + readVFS(new VFS::FileSystemArchive(name)); } else { - std::cerr << "ERROR: \"" << name << "\" is not a nif or bsa file!" << std::endl; + std::cerr << "ERROR: \"" << name << "\" is not a nif file, bsa file, or directory!" << std::endl; } } catch (std::exception& e) From 16d163bd3e28887c14c335dda3777704b2deb5a4 Mon Sep 17 00:00:00 2001 From: Arthur Moore Date: Fri, 7 Aug 2015 20:22:18 -0400 Subject: [PATCH 147/441] Updated niftest's help message --- apps/niftest/niftest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index c138e4551..c50e664b0 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -74,8 +74,8 @@ std::vector parseOptions (int argc, char** argv) { bpo::options_description desc("Ensure that OpenMW can use the provided NIF and BSA files\n\n" "Usages:\n" - " niftool \n" - " Scan the file for read errors.\n\n" + " niftool \n" + " Scan the file or directories for nif errors.\n\n" "Allowed options"); desc.add_options() ("help,h", "print help message.") From 8b48fe1cad22caea2a430ac22c498fdd1291cc73 Mon Sep 17 00:00:00 2001 From: Arthur Moore Date: Fri, 7 Aug 2015 20:39:43 -0400 Subject: [PATCH 148/441] niftest now scans BSA files in directories for nif errors The program is explicit so the user knows exactly where the bad file is. --- apps/niftest/niftest.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index c50e664b0..6229d1e83 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -45,9 +45,9 @@ bool isBSA(std::string filename) /// Check all the nif files in a given VFS::Archive /// \note Takes ownership! -void readVFS(VFS::Archive* anArchive) +void readVFS(VFS::Archive* anArchive,std::string archivePath = "") { - VFS::Manager myManager(false); + VFS::Manager myManager(true); myManager.addArchive(anArchive); myManager.buildIndex(); @@ -62,6 +62,15 @@ void readVFS(VFS::Archive* anArchive) // std::cout << "Decoding: " << name << std::endl; Nif::NIFFile temp_nif(myManager.get(name),name); } + else if(isBSA(name)) + { + if(!archivePath.empty()) + { + std::cout << "Reading BSA File: " << name << std::endl; + readVFS(new VFS::BsaArchive(archivePath+name)); + std::cout << "Done with BSA File: " << name << std::endl; + } + } } catch (std::exception& e) { @@ -139,7 +148,7 @@ int main(int argc, char **argv) else if(bfs::is_directory(bfs::path(name))) { std::cout << "Reading All Files in: " << name << std::endl; - readVFS(new VFS::FileSystemArchive(name)); + readVFS(new VFS::FileSystemArchive(name),name); } else { From 28bc0cca6c691cbcea52f668e9e1dfae9eed7d50 Mon Sep 17 00:00:00 2001 From: Arthur Moore Date: Fri, 7 Aug 2015 20:49:52 -0400 Subject: [PATCH 149/441] Cleaned up niftest's output A bad file inside of a bsa archive now looks like: /Data Files/TR_Data.bsa/meshes/tr/x/tr_act_ind_mark_alm.nif --- apps/niftest/niftest.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 6229d1e83..72393db40 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -45,6 +45,7 @@ bool isBSA(std::string filename) /// Check all the nif files in a given VFS::Archive /// \note Takes ownership! +/// \note Can not read a bsa file inside of a bsa file. void readVFS(VFS::Archive* anArchive,std::string archivePath = "") { VFS::Manager myManager(true); @@ -60,15 +61,15 @@ void readVFS(VFS::Archive* anArchive,std::string archivePath = "") if(isNIF(name)) { // std::cout << "Decoding: " << name << std::endl; - Nif::NIFFile temp_nif(myManager.get(name),name); + Nif::NIFFile temp_nif(myManager.get(name),archivePath+name); } else if(isBSA(name)) { - if(!archivePath.empty()) + if(!archivePath.empty() && !isBSA(archivePath)) { - std::cout << "Reading BSA File: " << name << std::endl; - readVFS(new VFS::BsaArchive(archivePath+name)); - std::cout << "Done with BSA File: " << name << std::endl; +// std::cout << "Reading BSA File: " << name << std::endl; + readVFS(new VFS::BsaArchive(archivePath+name),archivePath+name+"/"); +// std::cout << "Done with BSA File: " << name << std::endl; } } } @@ -129,7 +130,7 @@ int main(int argc, char **argv) { std::vector files = parseOptions (argc, argv); - std::cout << "Reading Files" << std::endl; +// std::cout << "Reading Files" << std::endl; for(std::vector::const_iterator it=files.begin(); it!=files.end(); ++it) { std::string name = *it; @@ -142,12 +143,12 @@ int main(int argc, char **argv) } else if(isBSA(name)) { - std::cout << "Reading BSA File: " << name << std::endl; +// std::cout << "Reading BSA File: " << name << std::endl; readVFS(new VFS::BsaArchive(name)); } else if(bfs::is_directory(bfs::path(name))) { - std::cout << "Reading All Files in: " << name << std::endl; +// std::cout << "Reading All Files in: " << name << std::endl; readVFS(new VFS::FileSystemArchive(name),name); } else From a7befc1f0ae6d31c6f5bd5428321d039c5a5a72a Mon Sep 17 00:00:00 2001 From: Arthur Moore Date: Fri, 7 Aug 2015 20:53:43 -0400 Subject: [PATCH 150/441] Removed now unneeded script --- apps/niftest/find_bad_nifs.sh | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100755 apps/niftest/find_bad_nifs.sh diff --git a/apps/niftest/find_bad_nifs.sh b/apps/niftest/find_bad_nifs.sh deleted file mode 100755 index 4b599f442..000000000 --- a/apps/niftest/find_bad_nifs.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -#Script to test all nif files (both loose, and in BSA archives) in data files directory - -#The user input as an absolute path -DATAFILESDIR="`readlink -m "$1"`" -#Program used to test -TEST_PROG="`pwd`/niftest" - -#Make sure our files don't bother anyone -NIFTEST_TMP_DIR="/tmp/niftest_$RANDOM/" -mkdir "$NIFTEST_TMP_DIR" -cd "$NIFTEST_TMP_DIR" - -find "$DATAFILESDIR" -iname *bsa > nifs.txt -find "$DATAFILESDIR" -iname *nif >> nifs.txt - -sed -e 's/.*/\"&\"/' nifs.txt > quoted_nifs.txt - -xargs --arg-file=quoted_nifs.txt "$TEST_PROG" 2>&1 | tee nif_out.txt -# xargs --arg-file=quoted_nifs.txt "$TEST_PROG" 2> nif_out.txt >/dev/null - -echo "List of bad NIF Files:" -cat nif_out.txt|grep File:|cut -d ' ' -f 2- - -rm nifs.txt -rm quoted_nifs.txt -rm nif_out.txt -rmdir "$NIFTEST_TMP_DIR" From ef02bc657f376ad87a064613b91eae9511f9d361 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Mon, 3 Aug 2015 19:08:01 +0300 Subject: [PATCH 151/441] Add magic effect verifier --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/tools/magiceffectcheck.cpp | 137 +++++++++++++++++++ apps/opencs/model/tools/magiceffectcheck.hpp | 50 +++++++ apps/opencs/model/tools/tools.cpp | 7 + 4 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/model/tools/magiceffectcheck.cpp create mode 100644 apps/opencs/model/tools/magiceffectcheck.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 1ae32bcbb..db8e866df 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -41,7 +41,7 @@ opencs_units (model/tools opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck - startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck + startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck ) diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp new file mode 100644 index 000000000..22620929c --- /dev/null +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -0,0 +1,137 @@ +#include "magiceffectcheck.hpp" + +#include + +#include "../world/resources.hpp" +#include "../world/data.hpp" + +namespace +{ + void addMessageIfNotEmpty(CSMDoc::Messages &messages, const CSMWorld::UniversalId &id, const std::string text) + { + if (!text.empty()) + { + messages.push_back(std::make_pair(id, text)); + } + } +} + +bool CSMTools::MagicEffectCheckStage::isTextureExists(const std::string &texture, bool isIcon) const +{ + const CSMWorld::Resources &textures = isIcon ? mIcons : mTextures; + bool exists = false; + + if (textures.searchId(texture) != -1) + { + exists = true; + } + else + { + std::string ddsTexture = texture; + if (Misc::ResourceHelpers::changeExtensionToDds(ddsTexture) && textures.searchId(ddsTexture) != -1) + { + exists = true; + } + } + + return exists; +} + +std::string CSMTools::MagicEffectCheckStage::checkReferenceable(const std::string &id, + const CSMWorld::UniversalId &type, + const std::string &column) const +{ + std::string error; + if (!id.empty()) + { + CSMWorld::RefIdData::LocalIndex index = mReferenceables.getDataSet().searchId(id); + if (index.first == -1) + { + error = "No such " + column + " '" + id + "'"; + } + else if (index.second != type.getType()) + { + error = column + " is not of type " + type.getTypeName(); + } + } + return error; +} + +std::string CSMTools::MagicEffectCheckStage::checkSound(const std::string &id, const std::string &column) const +{ + std::string error; + if (!id.empty() && mSounds.searchId(id) == -1) + { + error = "No such " + column + " '" + id + "'"; + } + return error; +} + +CSMTools::MagicEffectCheckStage::MagicEffectCheckStage(const CSMWorld::IdCollection &effects, + const CSMWorld::IdCollection &sounds, + const CSMWorld::RefIdCollection &referenceables, + const CSMWorld::Resources &icons, + const CSMWorld::Resources &textures) + : mMagicEffects(effects), + mSounds(sounds), + mReferenceables(referenceables), + mIcons(icons), + mTextures(textures) +{} + +int CSMTools::MagicEffectCheckStage::setup() +{ + return mMagicEffects.getSize(); +} + +void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messages) +{ + ESM::MagicEffect effect = mMagicEffects.getRecord(stage).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId); + + if (effect.mData.mBaseCost < 0.0f) + { + messages.push_back(std::make_pair(id, "Base Cost is negative")); + } + + if (effect.mIcon.empty()) + { + messages.push_back(std::make_pair(id, "Icon is not specified")); + } + else if (!isTextureExists(effect.mIcon, true)) + { + messages.push_back(std::make_pair(id, "No such Icon '" + effect.mIcon + "'")); + } + + if (effect.mParticle.empty()) + { + messages.push_back(std::make_pair(id, "Particle is not specified")); + } + else if (!isTextureExists(effect.mParticle, false)) + { + messages.push_back(std::make_pair(id, "No such Particle '" + effect.mParticle + "'")); + } + + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mCasting, CSMWorld::UniversalId::Type_Static, "Casting Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mHit, CSMWorld::UniversalId::Type_Static, "Hit Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mArea, CSMWorld::UniversalId::Type_Static, "Area Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mBolt, CSMWorld::UniversalId::Type_Weapon, "Bolt Object")); + + addMessageIfNotEmpty(messages, id, checkSound(effect.mCastSound, "Casting Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mHitSound, "Hit Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mAreaSound, "Area Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mBoltSound, "Bolt Sound")); + + if (effect.mDescription.empty()) + { + messages.push_back(std::make_pair(id, "Description is empty")); + } +} diff --git a/apps/opencs/model/tools/magiceffectcheck.hpp b/apps/opencs/model/tools/magiceffectcheck.hpp new file mode 100644 index 000000000..0ad6760d3 --- /dev/null +++ b/apps/opencs/model/tools/magiceffectcheck.hpp @@ -0,0 +1,50 @@ +#ifndef CSM_TOOLS_MAGICEFFECTCHECK_HPP +#define CSM_TOOLS_MAGICEFFECTCHECK_HPP + +#include +#include + +#include "../world/idcollection.hpp" +#include "../world/refidcollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMWorld +{ + class Resources; +} + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that magic effect records are internally consistent + class MagicEffectCheckStage : public CSMDoc::Stage + { + const CSMWorld::IdCollection &mMagicEffects; + const CSMWorld::IdCollection &mSounds; + const CSMWorld::RefIdCollection &mReferenceables; + const CSMWorld::Resources &mIcons; + const CSMWorld::Resources &mTextures; + + private: + bool isTextureExists(const std::string &texture, bool isIcon) const; + + std::string checkReferenceable(const std::string &id, + const CSMWorld::UniversalId &type, + const std::string &column) const; + std::string checkSound(const std::string &id, const std::string &column) const; + + public: + MagicEffectCheckStage(const CSMWorld::IdCollection &effects, + const CSMWorld::IdCollection &sounds, + const CSMWorld::RefIdCollection &referenceables, + const CSMWorld::Resources &icons, + const CSMWorld::Resources &textures); + + virtual int setup(); + ///< \return number of steps + virtual void perform (int stage, CSMDoc::Messages &messages); + ///< Messages resulting from this tage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index c9c116091..97943b1fb 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -28,6 +28,7 @@ #include "searchoperation.hpp" #include "pathgridcheck.hpp" #include "soundgencheck.hpp" +#include "magiceffectcheck.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -108,6 +109,12 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mData.getSounds(), mData.getReferenceables())); + mVerifierOperation->appendStage (new MagicEffectCheckStage (mData.getMagicEffects(), + mData.getSounds(), + mData.getReferenceables(), + mData.getResources (CSMWorld::UniversalId::Type_Icons), + mData.getResources (CSMWorld::UniversalId::Type_Textures))); + mVerifier.setOperation (mVerifierOperation); } From 11f5a928db2aa8b7f00819c459ec95efcea9fa75 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 4 Aug 2015 16:19:00 +0300 Subject: [PATCH 152/441] Don't allow empty value of School field in Magic Effects table --- apps/opencs/view/doc/viewmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 294efdc1b..86954db1e 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -97,7 +97,7 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_MeshType, CSMWorld::Columns::ColumnId_MeshType, false }, { CSMWorld::ColumnBase::Display_Gender, CSMWorld::Columns::ColumnId_Gender, true }, { CSMWorld::ColumnBase::Display_SoundGeneratorType, CSMWorld::Columns::ColumnId_SoundGeneratorType, false }, - { CSMWorld::ColumnBase::Display_School, CSMWorld::Columns::ColumnId_School, true }, + { CSMWorld::ColumnBase::Display_School, CSMWorld::Columns::ColumnId_School, false }, { CSMWorld::ColumnBase::Display_SkillImpact, CSMWorld::Columns::ColumnId_SkillImpact, true }, { CSMWorld::ColumnBase::Display_EffectRange, CSMWorld::Columns::ColumnId_EffectRange, false }, { CSMWorld::ColumnBase::Display_EffectId, CSMWorld::Columns::ColumnId_EffectId, false }, From f0982c87ba0b650e3313d49de20541a2b68ae457 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 6 Aug 2015 14:17:42 +0300 Subject: [PATCH 153/441] Remove check for an empty Particle from Magic effects verifier --- apps/opencs/model/tools/magiceffectcheck.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp index 22620929c..5435881b3 100644 --- a/apps/opencs/model/tools/magiceffectcheck.cpp +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -103,11 +103,7 @@ void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messa messages.push_back(std::make_pair(id, "No such Icon '" + effect.mIcon + "'")); } - if (effect.mParticle.empty()) - { - messages.push_back(std::make_pair(id, "Particle is not specified")); - } - else if (!isTextureExists(effect.mParticle, false)) + if (!effect.mParticle.empty() && !isTextureExists(effect.mParticle, false)) { messages.push_back(std::make_pair(id, "No such Particle '" + effect.mParticle + "'")); } From 0f2ea5764877596d3ba8805d4f528f2596bfb1b4 Mon Sep 17 00:00:00 2001 From: "taras.kudryavtsev" Date: Tue, 4 Aug 2015 16:56:05 +0300 Subject: [PATCH 154/441] #2730 and #2725 Conflicts: apps/opencs/view/world/dialoguesubview.cpp --- apps/opencs/model/world/columnbase.cpp | 1 - apps/opencs/model/world/columnbase.hpp | 1 - apps/opencs/model/world/refidcollection.cpp | 2 +- apps/opencs/view/doc/viewmanager.cpp | 2 +- apps/opencs/view/world/dialoguesubview.cpp | 24 ++++++++++++++------- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index f209e48c6..2143ec730 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -82,7 +82,6 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_EffectId, Display_PartRefType, Display_AiPackageType, - Display_YesNo, Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index c4789ed22..400e31333 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -118,7 +118,6 @@ namespace CSMWorld Display_EffectId, Display_PartRefType, Display_AiPackageType, - Display_YesNo, Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index fc52fe388..f3d50ee0c 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -208,7 +208,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderIdle, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_YesNo)); + new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String)); mColumns.back().addColumn( diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 86954db1e..0c9075990 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -103,7 +103,7 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_EffectId, CSMWorld::Columns::ColumnId_EffectId, false }, { CSMWorld::ColumnBase::Display_PartRefType, CSMWorld::Columns::ColumnId_PartRefType, false }, { CSMWorld::ColumnBase::Display_AiPackageType, CSMWorld::Columns::ColumnId_AiPackageType, false }, - { CSMWorld::ColumnBase::Display_YesNo, CSMWorld::Columns::ColumnId_AiWanderRepeat, false }, + { CSMWorld::ColumnBase::Display_Boolean, CSMWorld::Columns::ColumnId_AiWanderRepeat, false }, { CSMWorld::ColumnBase::Display_InfoCondFunc, CSMWorld::Columns::ColumnId_InfoCondFunc, false }, { CSMWorld::ColumnBase::Display_InfoCondComp, CSMWorld::Columns::ColumnId_InfoCondComp, false }, { CSMWorld::ColumnBase::Display_RaceSkill, CSMWorld::Columns::ColumnId_RaceSkill, true }, diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 5f6f1343d..b8683d2fe 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -747,8 +747,10 @@ CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::Universa mMainLayout = new QVBoxLayout(mainWidget); setWidget (mainWidget); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + mEditWidget = new EditWidget(mainWidget, - mTable->getModelIndex(getUniversalId().getId(), 0).row(), mTable, mCommandDispatcher, document, false); + mTable->getModelIndex(getUniversalId().getId(), idColumn).row(), mTable, mCommandDispatcher, document, false); if (id.getType() == CSMWorld::UniversalId::Type_Referenceable) { @@ -761,7 +763,7 @@ CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::Universa mMainLayout->addWidget(mEditWidget); mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - dataChanged(mTable->getModelIndex (getUniversalId().getId(), 0)); + dataChanged(mTable->getModelIndex (getUniversalId().getId(), idColumn)); connect(mEditWidget, SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), @@ -774,8 +776,9 @@ void CSVWorld::SimpleDialogueSubView::setEditLock (bool locked) if (!mEditWidget) // hack to indicate that getUniversalId().getId() is no longer valid return; + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); mLocked = locked; - QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), 0)); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (currentIndex.isValid()) { @@ -790,7 +793,8 @@ void CSVWorld::SimpleDialogueSubView::setEditLock (bool locked) void CSVWorld::SimpleDialogueSubView::dataChanged (const QModelIndex & index) { - QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && (index.parent().isValid() ? index.parent().row() : index.row()) == currentIndex.row()) @@ -823,7 +827,8 @@ void CSVWorld::SimpleDialogueSubView::dataChanged (const QModelIndex & index) void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { - QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (!currentIndex.isValid()) { @@ -944,7 +949,8 @@ void CSVWorld::DialogueSubView::updateUserSetting (const QString& name, const QS void CSVWorld::DialogueSubView::showPreview () { - QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && getTable().getFeatures() & CSMWorld::IdTable::Feature_Preview && @@ -956,7 +962,8 @@ void CSVWorld::DialogueSubView::showPreview () void CSVWorld::DialogueSubView::viewRecord () { - QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && currentIndex.row() < getTable().rowCount()) @@ -991,7 +998,8 @@ void CSVWorld::DialogueSubView::switchToRow (int row) void CSVWorld::DialogueSubView::requestFocus (const std::string& id) { - QModelIndex index = getTable().getModelIndex (id, 0); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex index = getTable().getModelIndex (id, idColumn); if (index.isValid()) switchToRow (index.row()); From 2f372a79b972e248e6b09e6e54ce9fb0d622cda0 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 8 Sep 2015 06:31:38 +1000 Subject: [PATCH 155/441] Remove additional instance of Display_YesNo (in cc9cii branch but not in master) --- apps/opencs/model/world/refidcollection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index f3d50ee0c..3f0c7904b 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -156,7 +156,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) new RefIdColumn (Columns::ColumnId_SpellType, CSMWorld::ColumnBase::Display_SpellType, false/*editable*/, false/*user editable*/)); // creatures do not have below columns mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_SpellSrc, CSMWorld::ColumnBase::Display_YesNo, false, false)); // from race + new RefIdColumn (Columns::ColumnId_SpellSrc, CSMWorld::ColumnBase::Display_Boolean, false, false)); // from race mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_SpellCost, CSMWorld::ColumnBase::Display_Integer, false, false)); mColumns.back().addColumn( From 34561379bc15f5d2d7c076acbe370c2951cfbc14 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 13 Aug 2015 17:01:25 +0200 Subject: [PATCH 156/441] Editor: fix magic effect magnitudes incorrectly labelled as Min/Max Range Conflicts: apps/opencs/model/world/columns.hpp --- apps/opencs/model/world/columns.cpp | 2 ++ apps/opencs/model/world/columns.hpp | 9 ++++++--- apps/opencs/model/world/data.cpp | 8 ++++---- apps/opencs/model/world/refidcollection.cpp | 4 ++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 329d8a7a5..eb622f457 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -35,6 +35,8 @@ namespace CSMWorld { ColumnId_Volume, "Volume" }, { ColumnId_MinRange, "Min Range" }, { ColumnId_MaxRange, "Max Range" }, + { ColumnId_MinMagnitude, "Min Magnitude" }, + { ColumnId_MaxMagnitude, "Max Magnitude" }, { ColumnId_SoundFile, "Sound File" }, { ColumnId_MapColour, "Map Colour" }, { ColumnId_SleepEncounter, "Sleep Encounter" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index e2c5712d6..b5b247433 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -306,9 +306,12 @@ namespace CSMWorld ColumnId_FileDescription = 276, ColumnId_Author = 277, - ColumnId_SpellSrc = 278, - ColumnId_SpellCost = 279, - ColumnId_SpellChance = 280, + ColumnId_MinMagnitude = 278, + ColumnId_MaxMagnitude = 279, + + ColumnId_SpellSrc = 280, + ColumnId_SpellCost = 281, + ColumnId_SpellChance = 282, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 7b3e1fe4f..a86e2ebc8 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -293,9 +293,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); mTopics.addColumn (new StringIdColumn); mTopics.addColumn (new RecordStateColumn); @@ -409,9 +409,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); mBodyParts.addColumn (new StringIdColumn); mBodyParts.addColumn (new RecordStateColumn); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 3f0c7904b..9b0a443ec 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -94,9 +94,9 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); EnchantableColumns enchantableColumns (inventoryColumns); From 1d2f4c8cfe77e6485c139ed2593f84b40ad0dea6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 15 Aug 2015 18:39:00 +0200 Subject: [PATCH 157/441] Editor: remove creature flag of unknown purpose from the UI --- apps/opencs/model/world/columns.cpp | 1 - apps/opencs/model/world/columns.hpp | 2 +- apps/opencs/model/world/refidcollection.cpp | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index eb622f457..00608423c 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -109,7 +109,6 @@ namespace CSMWorld { ColumnId_OriginalCreature, "Original Creature" }, { ColumnId_Biped, "Biped" }, { ColumnId_HasWeapon, "Has Weapon" }, - { ColumnId_NoMovement, "No Movement" }, { ColumnId_Swims, "Swims" }, { ColumnId_Flies, "Flies" }, { ColumnId_Walks, "Walks" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index b5b247433..61b796581 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -102,7 +102,7 @@ namespace CSMWorld ColumnId_OriginalCreature = 87, ColumnId_Biped = 88, ColumnId_HasWeapon = 89, - ColumnId_NoMovement = 90, + // unused ColumnId_Swims = 91, ColumnId_Flies = 92, ColumnId_Walks = 93, diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 9b0a443ec..d49f8f5b7 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -331,7 +331,6 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) { { Columns::ColumnId_Biped, ESM::Creature::Bipedal }, { Columns::ColumnId_HasWeapon, ESM::Creature::Weapon }, - { Columns::ColumnId_NoMovement, ESM::Creature::None }, { Columns::ColumnId_Swims, ESM::Creature::Swims }, { Columns::ColumnId_Flies, ESM::Creature::Flies }, { Columns::ColumnId_Walks, ESM::Creature::Walks }, From dae2588c8820cb3480097d129c0df49fe96e74f2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 16 Aug 2015 23:42:08 +0200 Subject: [PATCH 158/441] Editor: fix a typo in ESM::Light flag mappings --- apps/opencs/model/world/refidcollection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index d49f8f5b7..8ea2bb30a 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -390,7 +390,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) { Columns::ColumnId_Portable, ESM::Light::Carry }, { Columns::ColumnId_NegativeLight, ESM::Light::Negative }, { Columns::ColumnId_Flickering, ESM::Light::Flicker }, - { Columns::ColumnId_SlowFlickering, ESM::Light::Flicker }, + { Columns::ColumnId_SlowFlickering, ESM::Light::FlickerSlow }, { Columns::ColumnId_Pulsing, ESM::Light::Pulse }, { Columns::ColumnId_SlowPulsing, ESM::Light::PulseSlow }, { Columns::ColumnId_Fire, ESM::Light::Fire }, From f8f66bffb091d531cf9a68b98c1b25c9a81d9fb5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 18 Aug 2015 23:06:12 +0200 Subject: [PATCH 159/441] Remove empty line at the beginning of files git ls-files -z | xargs -0 sed -i '1{/^$/d}' --- apps/opencs/editor.cpp | 1 - apps/opencs/main.cpp | 1 - apps/opencs/model/doc/blacklist.cpp | 1 - apps/opencs/model/doc/documentmanager.cpp | 1 - apps/opencs/model/doc/loader.cpp | 1 - apps/opencs/model/doc/messages.cpp | 1 - apps/opencs/model/doc/operation.cpp | 1 - apps/opencs/model/doc/operationholder.cpp | 1 - apps/opencs/model/doc/runner.cpp | 1 - apps/opencs/model/doc/saving.cpp | 1 - apps/opencs/model/doc/savingstages.cpp | 1 - apps/opencs/model/doc/savingstate.cpp | 1 - apps/opencs/model/doc/stage.cpp | 1 - apps/opencs/model/filter/andnode.cpp | 1 - apps/opencs/model/filter/booleannode.cpp | 1 - apps/opencs/model/filter/leafnode.cpp | 1 - apps/opencs/model/filter/narynode.cpp | 1 - apps/opencs/model/filter/node.cpp | 1 - apps/opencs/model/filter/notnode.cpp | 1 - apps/opencs/model/filter/ornode.cpp | 1 - apps/opencs/model/filter/parser.cpp | 1 - apps/opencs/model/filter/textnode.cpp | 1 - apps/opencs/model/filter/unarynode.cpp | 1 - apps/opencs/model/filter/valuenode.cpp | 1 - apps/opencs/model/tools/birthsigncheck.cpp | 1 - apps/opencs/model/tools/classcheck.cpp | 1 - apps/opencs/model/tools/factioncheck.cpp | 1 - apps/opencs/model/tools/mandatoryid.cpp | 1 - apps/opencs/model/tools/racecheck.cpp | 1 - apps/opencs/model/tools/regioncheck.cpp | 1 - apps/opencs/model/tools/reportmodel.cpp | 1 - apps/opencs/model/tools/scriptcheck.cpp | 1 - apps/opencs/model/tools/search.cpp | 1 - apps/opencs/model/tools/searchoperation.cpp | 1 - apps/opencs/model/tools/searchstage.cpp | 1 - apps/opencs/model/tools/skillcheck.cpp | 1 - apps/opencs/model/tools/soundcheck.cpp | 1 - apps/opencs/model/tools/spellcheck.cpp | 1 - apps/opencs/model/tools/startscriptcheck.cpp | 1 - apps/opencs/model/tools/tools.cpp | 1 - apps/opencs/model/world/cell.cpp | 1 - apps/opencs/model/world/cellcoordinates.cpp | 1 - apps/opencs/model/world/cellselection.cpp | 1 - apps/opencs/model/world/collectionbase.cpp | 1 - apps/opencs/model/world/columns.cpp | 1 - apps/opencs/model/world/commanddispatcher.cpp | 1 - apps/opencs/model/world/data.cpp | 1 - apps/opencs/model/world/idtablebase.cpp | 1 - apps/opencs/model/world/idtableproxymodel.cpp | 1 - apps/opencs/model/world/infocollection.cpp | 1 - apps/opencs/model/world/metadata.cpp | 1 - apps/opencs/model/world/record.cpp | 1 - apps/opencs/model/world/ref.cpp | 1 - apps/opencs/model/world/refcollection.cpp | 1 - apps/opencs/model/world/refiddata.cpp | 1 - apps/opencs/model/world/regionmap.cpp | 1 - apps/opencs/model/world/resources.cpp | 1 - apps/opencs/model/world/resourcesmanager.cpp | 1 - apps/opencs/model/world/resourcetable.cpp | 1 - apps/opencs/model/world/scope.cpp | 1 - apps/opencs/model/world/scriptcontext.cpp | 1 - apps/opencs/model/world/tablemimedata.hpp | 1 - apps/opencs/model/world/universalid.cpp | 1 - apps/opencs/view/doc/adjusterwidget.cpp | 1 - apps/opencs/view/doc/filewidget.cpp | 1 - apps/opencs/view/doc/globaldebugprofilemenu.cpp | 1 - apps/opencs/view/doc/loader.cpp | 1 - apps/opencs/view/doc/newgame.cpp | 1 - apps/opencs/view/doc/runlogsubview.cpp | 1 - apps/opencs/view/doc/startup.cpp | 1 - apps/opencs/view/doc/subviewfactory.cpp | 1 - apps/opencs/view/doc/viewmanager.cpp | 1 - apps/opencs/view/filter/editwidget.cpp | 1 - apps/opencs/view/filter/filterbox.cpp | 1 - apps/opencs/view/filter/recordfilterbox.cpp | 1 - apps/opencs/view/render/cell.cpp | 1 - apps/opencs/view/render/editmode.cpp | 1 - apps/opencs/view/render/lightingbright.cpp | 1 - apps/opencs/view/render/pagedworldspacewidget.cpp | 1 - apps/opencs/view/render/previewwidget.cpp | 1 - apps/opencs/view/render/unpagedworldspacewidget.cpp | 1 - apps/opencs/view/render/worldspacewidget.cpp | 1 - apps/opencs/view/tools/reportsubview.cpp | 1 - apps/opencs/view/tools/reporttable.cpp | 1 - apps/opencs/view/tools/searchbox.cpp | 1 - apps/opencs/view/tools/searchsubview.cpp | 1 - apps/opencs/view/tools/subviews.cpp | 1 - apps/opencs/view/widget/modebutton.cpp | 1 - apps/opencs/view/widget/pushbutton.cpp | 1 - apps/opencs/view/widget/scenetool.cpp | 1 - apps/opencs/view/widget/scenetoolbar.cpp | 1 - apps/opencs/view/widget/scenetoolmode.cpp | 1 - apps/opencs/view/widget/scenetoolrun.cpp | 1 - apps/opencs/view/widget/scenetooltoggle.cpp | 1 - apps/opencs/view/widget/scenetooltoggle2.cpp | 1 - apps/opencs/view/world/cellcreator.cpp | 1 - apps/opencs/view/world/creator.cpp | 1 - apps/opencs/view/world/dialoguecreator.cpp | 1 - apps/opencs/view/world/enumdelegate.cpp | 1 - apps/opencs/view/world/genericcreator.cpp | 1 - apps/opencs/view/world/idvalidator.cpp | 1 - apps/opencs/view/world/infocreator.cpp | 1 - apps/opencs/view/world/previewsubview.cpp | 1 - apps/opencs/view/world/recordbuttonbar.cpp | 1 - apps/opencs/view/world/referenceablecreator.cpp | 1 - apps/opencs/view/world/referencecreator.cpp | 1 - apps/opencs/view/world/regionmap.cpp | 1 - apps/opencs/view/world/regionmapsubview.cpp | 1 - apps/opencs/view/world/scenesubview.cpp | 1 - apps/opencs/view/world/scripterrortable.cpp | 1 - apps/opencs/view/world/scripthighlighter.cpp | 1 - apps/opencs/view/world/subviews.cpp | 1 - apps/opencs/view/world/table.cpp | 1 - apps/opencs/view/world/tablebottombox.cpp | 1 - apps/opencs/view/world/tablesubview.cpp | 1 - apps/opencs/view/world/util.cpp | 1 - apps/opencs/view/world/vartypedelegate.cpp | 1 - apps/openmw/android_commandLine.h | 1 - apps/openmw/mwbase/environment.cpp | 1 - apps/openmw/mwclass/activator.cpp | 1 - apps/openmw/mwclass/apparatus.cpp | 1 - apps/openmw/mwclass/armor.cpp | 1 - apps/openmw/mwclass/classes.cpp | 1 - apps/openmw/mwclass/clothing.cpp | 1 - apps/openmw/mwclass/container.cpp | 1 - apps/openmw/mwclass/creature.cpp | 1 - apps/openmw/mwclass/creaturelevlist.cpp | 1 - apps/openmw/mwclass/door.cpp | 1 - apps/openmw/mwclass/ingredient.cpp | 1 - apps/openmw/mwclass/itemlevlist.cpp | 1 - apps/openmw/mwclass/light.cpp | 1 - apps/openmw/mwclass/lockpick.cpp | 1 - apps/openmw/mwclass/misc.cpp | 1 - apps/openmw/mwclass/npc.cpp | 1 - apps/openmw/mwclass/potion.cpp | 1 - apps/openmw/mwclass/probe.cpp | 1 - apps/openmw/mwclass/repair.cpp | 1 - apps/openmw/mwclass/static.cpp | 1 - apps/openmw/mwclass/weapon.cpp | 1 - apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 1 - apps/openmw/mwdialogue/filter.cpp | 1 - apps/openmw/mwdialogue/journalentry.cpp | 1 - apps/openmw/mwdialogue/journalimp.cpp | 1 - apps/openmw/mwdialogue/quest.cpp | 1 - apps/openmw/mwdialogue/selectwrapper.cpp | 1 - apps/openmw/mwdialogue/topic.cpp | 1 - apps/openmw/mwgui/tooltips.hpp | 1 - apps/openmw/mwmechanics/alchemy.cpp | 1 - apps/openmw/mwmechanics/magiceffects.cpp | 1 - apps/openmw/mwmechanics/npcstats.cpp | 1 - apps/openmw/mwmechanics/spells.cpp | 1 - apps/openmw/mwscript/aiextensions.cpp | 1 - apps/openmw/mwscript/animationextensions.cpp | 1 - apps/openmw/mwscript/compilercontext.cpp | 1 - apps/openmw/mwscript/consoleextensions.cpp | 1 - apps/openmw/mwscript/containerextensions.cpp | 1 - apps/openmw/mwscript/controlextensions.cpp | 1 - apps/openmw/mwscript/dialogueextensions.cpp | 1 - apps/openmw/mwscript/extensions.cpp | 1 - apps/openmw/mwscript/globalscripts.cpp | 1 - apps/openmw/mwscript/guiextensions.cpp | 1 - apps/openmw/mwscript/interpretercontext.cpp | 1 - apps/openmw/mwscript/miscextensions.cpp | 1 - apps/openmw/mwscript/scriptmanagerimp.cpp | 1 - apps/openmw/mwscript/skyextensions.cpp | 1 - apps/openmw/mwscript/soundextensions.cpp | 1 - apps/openmw/mwscript/userextensions.cpp | 1 - apps/openmw/mwstate/character.cpp | 1 - apps/openmw/mwstate/charactermanager.cpp | 1 - apps/openmw/mwstate/statemanagerimp.cpp | 1 - apps/openmw/mwworld/action.cpp | 1 - apps/openmw/mwworld/actionapply.cpp | 1 - apps/openmw/mwworld/actioneat.cpp | 1 - apps/openmw/mwworld/actionopen.hpp | 1 - apps/openmw/mwworld/actiontake.cpp | 1 - apps/openmw/mwworld/actiontalk.cpp | 1 - apps/openmw/mwworld/class.cpp | 1 - apps/openmw/mwworld/containerstore.cpp | 1 - apps/openmw/mwworld/globals.cpp | 1 - apps/openmw/mwworld/inventorystore.cpp | 1 - apps/openmw/mwworld/ptr.cpp | 1 - apps/openmw/mwworld/refdata.cpp | 1 - components/compiler/controlparser.cpp | 1 - components/compiler/declarationparser.cpp | 1 - components/compiler/discardparser.cpp | 1 - components/compiler/errorhandler.cpp | 1 - components/compiler/errorhandler.hpp | 1 - components/compiler/exprparser.cpp | 1 - components/compiler/extensions.cpp | 1 - components/compiler/generator.cpp | 1 - components/compiler/junkparser.cpp | 1 - components/compiler/lineparser.cpp | 1 - components/compiler/literals.cpp | 1 - components/compiler/locals.cpp | 1 - components/compiler/nullerrorhandler.cpp | 1 - components/compiler/nullerrorhandler.hpp | 1 - components/compiler/output.cpp | 1 - components/compiler/parser.cpp | 1 - components/compiler/quickfileparser.cpp | 1 - components/compiler/scanner.cpp | 1 - components/compiler/scriptparser.cpp | 1 - components/compiler/skipparser.cpp | 1 - components/compiler/streamerrorhandler.cpp | 1 - components/compiler/streamerrorhandler.hpp | 1 - components/compiler/stringparser.cpp | 1 - components/esm/cellid.cpp | 1 - components/esm/cellref.cpp | 1 - components/esm/cellstate.cpp | 1 - components/esm/containerstate.cpp | 1 - components/esm/creaturestate.cpp | 1 - components/esm/debugprofile.cpp | 1 - components/esm/dialoguestate.cpp | 1 - components/esm/filter.cpp | 1 - components/esm/globalscript.cpp | 1 - components/esm/inventorystate.cpp | 1 - components/esm/journalentry.cpp | 1 - components/esm/loadtes3.cpp | 1 - components/esm/locals.cpp | 1 - components/esm/npcstate.cpp | 1 - components/esm/objectstate.cpp | 1 - components/esm/player.cpp | 1 - components/esm/queststate.cpp | 1 - components/esm/savedgame.cpp | 1 - components/esm/variantimp.cpp | 1 - components/files/multidircollection.cpp | 1 - components/interpreter/installopcodes.cpp | 1 - components/interpreter/interpreter.cpp | 1 - components/interpreter/runtime.cpp | 1 - files/mygui/CMakeLists.txt | 1 - 229 files changed, 229 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index c70b3dd19..bca21ff24 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -1,4 +1,3 @@ - #include "editor.hpp" #include diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index ce434fc43..db6531dc6 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -1,4 +1,3 @@ - #include "editor.hpp" #include diff --git a/apps/opencs/model/doc/blacklist.cpp b/apps/opencs/model/doc/blacklist.cpp index 083726412..b1d402c69 100644 --- a/apps/opencs/model/doc/blacklist.cpp +++ b/apps/opencs/model/doc/blacklist.cpp @@ -1,4 +1,3 @@ - #include "blacklist.hpp" #include diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 29d7a8d3a..070c15f9a 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -1,4 +1,3 @@ - #include "documentmanager.hpp" #include diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index 33725a6f9..cb3ff2cd0 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -1,4 +1,3 @@ - #include "loader.hpp" #include diff --git a/apps/opencs/model/doc/messages.cpp b/apps/opencs/model/doc/messages.cpp index c8d26d39b..86e96a88d 100644 --- a/apps/opencs/model/doc/messages.cpp +++ b/apps/opencs/model/doc/messages.cpp @@ -1,4 +1,3 @@ - #include "messages.hpp" CSMDoc::Message::Message() {} diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 8b2717086..cb9b7ec29 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -1,4 +1,3 @@ - #include "operation.hpp" #include diff --git a/apps/opencs/model/doc/operationholder.cpp b/apps/opencs/model/doc/operationholder.cpp index 25fc6fc26..db0d1a9a4 100644 --- a/apps/opencs/model/doc/operationholder.cpp +++ b/apps/opencs/model/doc/operationholder.cpp @@ -1,4 +1,3 @@ - #include "operationholder.hpp" #include "../settings/usersettings.hpp" diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 14fe0cda8..5a0bc39be 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -1,4 +1,3 @@ - #include "runner.hpp" #include diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 9f6e469b8..25372f1a6 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -1,4 +1,3 @@ - #include "saving.hpp" #include "../world/data.hpp" diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index f78c57ecd..ef3b23ec8 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -1,4 +1,3 @@ - #include "savingstages.hpp" #include diff --git a/apps/opencs/model/doc/savingstate.cpp b/apps/opencs/model/doc/savingstate.cpp index e7ad551b2..10539c1b5 100644 --- a/apps/opencs/model/doc/savingstate.cpp +++ b/apps/opencs/model/doc/savingstate.cpp @@ -1,4 +1,3 @@ - #include "savingstate.hpp" #include "operation.hpp" diff --git a/apps/opencs/model/doc/stage.cpp b/apps/opencs/model/doc/stage.cpp index 78aa14574..c8da86069 100644 --- a/apps/opencs/model/doc/stage.cpp +++ b/apps/opencs/model/doc/stage.cpp @@ -1,4 +1,3 @@ - #include "stage.hpp" CSMDoc::Stage::~Stage() {} diff --git a/apps/opencs/model/filter/andnode.cpp b/apps/opencs/model/filter/andnode.cpp index 4249fc228..908662799 100644 --- a/apps/opencs/model/filter/andnode.cpp +++ b/apps/opencs/model/filter/andnode.cpp @@ -1,4 +1,3 @@ - #include "andnode.hpp" #include diff --git a/apps/opencs/model/filter/booleannode.cpp b/apps/opencs/model/filter/booleannode.cpp index 35fc98e08..ee7ddc1c0 100644 --- a/apps/opencs/model/filter/booleannode.cpp +++ b/apps/opencs/model/filter/booleannode.cpp @@ -1,4 +1,3 @@ - #include "booleannode.hpp" CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {} diff --git a/apps/opencs/model/filter/leafnode.cpp b/apps/opencs/model/filter/leafnode.cpp index 055a1747c..6745e165e 100644 --- a/apps/opencs/model/filter/leafnode.cpp +++ b/apps/opencs/model/filter/leafnode.cpp @@ -1,4 +1,3 @@ - #include "leafnode.hpp" std::vector CSMFilter::LeafNode::getReferencedColumns() const diff --git a/apps/opencs/model/filter/narynode.cpp b/apps/opencs/model/filter/narynode.cpp index 98f706c87..f2e0e5cb2 100644 --- a/apps/opencs/model/filter/narynode.cpp +++ b/apps/opencs/model/filter/narynode.cpp @@ -1,4 +1,3 @@ - #include "narynode.hpp" #include diff --git a/apps/opencs/model/filter/node.cpp b/apps/opencs/model/filter/node.cpp index 091dc4698..e25610675 100644 --- a/apps/opencs/model/filter/node.cpp +++ b/apps/opencs/model/filter/node.cpp @@ -1,4 +1,3 @@ - #include "node.hpp" CSMFilter::Node::Node() {} diff --git a/apps/opencs/model/filter/notnode.cpp b/apps/opencs/model/filter/notnode.cpp index b5d9da7b7..ba5302bbe 100644 --- a/apps/opencs/model/filter/notnode.cpp +++ b/apps/opencs/model/filter/notnode.cpp @@ -1,4 +1,3 @@ - #include "notnode.hpp" CSMFilter::NotNode::NotNode (boost::shared_ptr child) : UnaryNode (child, "not") {} diff --git a/apps/opencs/model/filter/ornode.cpp b/apps/opencs/model/filter/ornode.cpp index c5d15a384..41ec7b5cf 100644 --- a/apps/opencs/model/filter/ornode.cpp +++ b/apps/opencs/model/filter/ornode.cpp @@ -1,4 +1,3 @@ - #include "ornode.hpp" #include diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 51338dfc9..7936a1ae2 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -1,4 +1,3 @@ - #include "parser.hpp" #include diff --git a/apps/opencs/model/filter/textnode.cpp b/apps/opencs/model/filter/textnode.cpp index 73c378f11..246ebae24 100644 --- a/apps/opencs/model/filter/textnode.cpp +++ b/apps/opencs/model/filter/textnode.cpp @@ -1,4 +1,3 @@ - #include "textnode.hpp" #include diff --git a/apps/opencs/model/filter/unarynode.cpp b/apps/opencs/model/filter/unarynode.cpp index c40d191b6..cbdadf6fc 100644 --- a/apps/opencs/model/filter/unarynode.cpp +++ b/apps/opencs/model/filter/unarynode.cpp @@ -1,4 +1,3 @@ - #include "unarynode.hpp" CSMFilter::UnaryNode::UnaryNode (boost::shared_ptr child, const std::string& name) diff --git a/apps/opencs/model/filter/valuenode.cpp b/apps/opencs/model/filter/valuenode.cpp index 6fdb5cb02..85c5a8e27 100644 --- a/apps/opencs/model/filter/valuenode.cpp +++ b/apps/opencs/model/filter/valuenode.cpp @@ -1,4 +1,3 @@ - #include "valuenode.hpp" #include diff --git a/apps/opencs/model/tools/birthsigncheck.cpp b/apps/opencs/model/tools/birthsigncheck.cpp index 4e6da4631..9898352f1 100644 --- a/apps/opencs/model/tools/birthsigncheck.cpp +++ b/apps/opencs/model/tools/birthsigncheck.cpp @@ -1,4 +1,3 @@ - #include "birthsigncheck.hpp" #include diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp index be57a3729..e4964d4e3 100644 --- a/apps/opencs/model/tools/classcheck.cpp +++ b/apps/opencs/model/tools/classcheck.cpp @@ -1,4 +1,3 @@ - #include "classcheck.hpp" #include diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index 0dfdee775..621b28070 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -1,4 +1,3 @@ - #include "factioncheck.hpp" #include diff --git a/apps/opencs/model/tools/mandatoryid.cpp b/apps/opencs/model/tools/mandatoryid.cpp index 4c97d2266..23adb9d37 100644 --- a/apps/opencs/model/tools/mandatoryid.cpp +++ b/apps/opencs/model/tools/mandatoryid.cpp @@ -1,4 +1,3 @@ - #include "mandatoryid.hpp" #include "../world/collectionbase.hpp" diff --git a/apps/opencs/model/tools/racecheck.cpp b/apps/opencs/model/tools/racecheck.cpp index 3b2c8d290..b30088620 100644 --- a/apps/opencs/model/tools/racecheck.cpp +++ b/apps/opencs/model/tools/racecheck.cpp @@ -1,4 +1,3 @@ - #include "racecheck.hpp" #include diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 42abc35c9..2fdff5f38 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -1,4 +1,3 @@ - #include "regioncheck.hpp" #include diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 4bf7d1581..77a14de84 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -1,4 +1,3 @@ - #include "reportmodel.hpp" #include diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index 665edd7a3..d7c41cfcf 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -1,4 +1,3 @@ - #include "scriptcheck.hpp" #include diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp index 449df2c63..0409199af 100644 --- a/apps/opencs/model/tools/search.cpp +++ b/apps/opencs/model/tools/search.cpp @@ -1,4 +1,3 @@ - #include "search.hpp" #include diff --git a/apps/opencs/model/tools/searchoperation.cpp b/apps/opencs/model/tools/searchoperation.cpp index 8cbc5dc8e..8fba1cc1e 100644 --- a/apps/opencs/model/tools/searchoperation.cpp +++ b/apps/opencs/model/tools/searchoperation.cpp @@ -1,4 +1,3 @@ - #include "searchoperation.hpp" #include "../doc/state.hpp" diff --git a/apps/opencs/model/tools/searchstage.cpp b/apps/opencs/model/tools/searchstage.cpp index 17859d930..3db10b0c3 100644 --- a/apps/opencs/model/tools/searchstage.cpp +++ b/apps/opencs/model/tools/searchstage.cpp @@ -1,4 +1,3 @@ - #include "searchstage.hpp" #include "../world/idtablebase.hpp" diff --git a/apps/opencs/model/tools/skillcheck.cpp b/apps/opencs/model/tools/skillcheck.cpp index 2b55526e0..77ba8d4a2 100644 --- a/apps/opencs/model/tools/skillcheck.cpp +++ b/apps/opencs/model/tools/skillcheck.cpp @@ -1,4 +1,3 @@ - #include "skillcheck.hpp" #include diff --git a/apps/opencs/model/tools/soundcheck.cpp b/apps/opencs/model/tools/soundcheck.cpp index f78932a32..6a059bee2 100644 --- a/apps/opencs/model/tools/soundcheck.cpp +++ b/apps/opencs/model/tools/soundcheck.cpp @@ -1,4 +1,3 @@ - #include "soundcheck.hpp" #include diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp index bd076d2a5..91aed37ed 100644 --- a/apps/opencs/model/tools/spellcheck.cpp +++ b/apps/opencs/model/tools/spellcheck.cpp @@ -1,4 +1,3 @@ - #include "spellcheck.hpp" #include diff --git a/apps/opencs/model/tools/startscriptcheck.cpp b/apps/opencs/model/tools/startscriptcheck.cpp index e3c01368b..220751797 100644 --- a/apps/opencs/model/tools/startscriptcheck.cpp +++ b/apps/opencs/model/tools/startscriptcheck.cpp @@ -1,4 +1,3 @@ - #include "startscriptcheck.hpp" #include diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 97943b1fb..0c6e36a61 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -1,4 +1,3 @@ - #include "tools.hpp" #include diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index 40520a9ba..91becdb74 100644 --- a/apps/opencs/model/world/cell.cpp +++ b/apps/opencs/model/world/cell.cpp @@ -1,4 +1,3 @@ - #include "cell.hpp" #include diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp index b1c8441e6..95e206e2d 100644 --- a/apps/opencs/model/world/cellcoordinates.cpp +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -1,4 +1,3 @@ - #include "cellcoordinates.hpp" #include diff --git a/apps/opencs/model/world/cellselection.cpp b/apps/opencs/model/world/cellselection.cpp index 73b5196f1..c6988e488 100644 --- a/apps/opencs/model/world/cellselection.cpp +++ b/apps/opencs/model/world/cellselection.cpp @@ -1,4 +1,3 @@ - #include "cellselection.hpp" #include diff --git a/apps/opencs/model/world/collectionbase.cpp b/apps/opencs/model/world/collectionbase.cpp index b8eed4192..6134dc172 100644 --- a/apps/opencs/model/world/collectionbase.cpp +++ b/apps/opencs/model/world/collectionbase.cpp @@ -1,4 +1,3 @@ - #include "collectionbase.hpp" #include diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 00608423c..bc6d75a18 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -1,4 +1,3 @@ - #include "columns.hpp" #include diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index b9d5bd7fe..0b1af0e84 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -1,4 +1,3 @@ - #include "commanddispatcher.hpp" #include diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index a86e2ebc8..80cdcb6b1 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1,4 +1,3 @@ - #include "data.hpp" #include diff --git a/apps/opencs/model/world/idtablebase.cpp b/apps/opencs/model/world/idtablebase.cpp index 389f5396e..274446b79 100644 --- a/apps/opencs/model/world/idtablebase.cpp +++ b/apps/opencs/model/world/idtablebase.cpp @@ -1,4 +1,3 @@ - #include "idtablebase.hpp" CSMWorld::IdTableBase::IdTableBase (unsigned int features) : mFeatures (features) {} diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 757285412..fbf7b6cf3 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -1,4 +1,3 @@ - #include "idtableproxymodel.hpp" #include diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 560be8131..60c613041 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -1,4 +1,3 @@ - #include "infocollection.hpp" #include diff --git a/apps/opencs/model/world/metadata.cpp b/apps/opencs/model/world/metadata.cpp index 40b8e9519..960fdc9e4 100644 --- a/apps/opencs/model/world/metadata.cpp +++ b/apps/opencs/model/world/metadata.cpp @@ -1,4 +1,3 @@ - #include "metadata.hpp" #include diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp index ef2f4d320..f13a36afc 100644 --- a/apps/opencs/model/world/record.cpp +++ b/apps/opencs/model/world/record.cpp @@ -1,4 +1,3 @@ - #include "record.hpp" CSMWorld::RecordBase::~RecordBase() {} diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index 13706c950..638f7ec9c 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -1,4 +1,3 @@ - #include "ref.hpp" #include diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index ff30dafae..f8818807b 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,4 +1,3 @@ - #include "refcollection.hpp" #include diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 7f5c25f36..c391201e6 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -1,4 +1,3 @@ - #include "refiddata.hpp" #include diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 42bde9c81..10c67c909 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -1,4 +1,3 @@ - #include "regionmap.hpp" #include diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 8c9439890..8bfd83248 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -1,4 +1,3 @@ - #include "resources.hpp" #include diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index deddd83b5..1b4770197 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -1,4 +1,3 @@ - #include "resourcesmanager.hpp" #include diff --git a/apps/opencs/model/world/resourcetable.cpp b/apps/opencs/model/world/resourcetable.cpp index 2cd44781a..5227ec3e6 100644 --- a/apps/opencs/model/world/resourcetable.cpp +++ b/apps/opencs/model/world/resourcetable.cpp @@ -1,4 +1,3 @@ - #include "resourcetable.hpp" #include diff --git a/apps/opencs/model/world/scope.cpp b/apps/opencs/model/world/scope.cpp index 6e4ce4c02..b026c34c3 100644 --- a/apps/opencs/model/world/scope.cpp +++ b/apps/opencs/model/world/scope.cpp @@ -1,4 +1,3 @@ - #include "scope.hpp" #include diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp index a8c2c9452..bcbca4b28 100644 --- a/apps/opencs/model/world/scriptcontext.cpp +++ b/apps/opencs/model/world/scriptcontext.cpp @@ -1,4 +1,3 @@ - #include "scriptcontext.hpp" #include diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index 06d252435..a42e97561 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -1,4 +1,3 @@ - #ifndef TABLEMIMEDATA_H #define TABLEMIMEDATA_H diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 8ad9873fc..4c6601e52 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -1,4 +1,3 @@ - #include "universalid.hpp" #include diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 6571ad7c8..ba5dd90f8 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -1,4 +1,3 @@ - #include "adjusterwidget.hpp" #include diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index f18fe695a..bbb824823 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -1,4 +1,3 @@ - #include "filewidget.hpp" #include diff --git a/apps/opencs/view/doc/globaldebugprofilemenu.cpp b/apps/opencs/view/doc/globaldebugprofilemenu.cpp index b88381385..f0d9655dd 100644 --- a/apps/opencs/view/doc/globaldebugprofilemenu.cpp +++ b/apps/opencs/view/doc/globaldebugprofilemenu.cpp @@ -1,4 +1,3 @@ - #include "globaldebugprofilemenu.hpp" #include diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index 30235d0f5..713295d70 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -1,4 +1,3 @@ - #include "loader.hpp" #include diff --git a/apps/opencs/view/doc/newgame.cpp b/apps/opencs/view/doc/newgame.cpp index 32b483728..b3e2a4f63 100644 --- a/apps/opencs/view/doc/newgame.cpp +++ b/apps/opencs/view/doc/newgame.cpp @@ -1,4 +1,3 @@ - #include "newgame.hpp" #include diff --git a/apps/opencs/view/doc/runlogsubview.cpp b/apps/opencs/view/doc/runlogsubview.cpp index 129396999..2b7182228 100644 --- a/apps/opencs/view/doc/runlogsubview.cpp +++ b/apps/opencs/view/doc/runlogsubview.cpp @@ -1,4 +1,3 @@ - #include "runlogsubview.hpp" #include diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 58a46c603..a9d697f1c 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -1,4 +1,3 @@ - #include "startup.hpp" #include diff --git a/apps/opencs/view/doc/subviewfactory.cpp b/apps/opencs/view/doc/subviewfactory.cpp index 3137f7e32..82a8aeb05 100644 --- a/apps/opencs/view/doc/subviewfactory.cpp +++ b/apps/opencs/view/doc/subviewfactory.cpp @@ -1,4 +1,3 @@ - #include "subviewfactory.hpp" #include diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 0c9075990..ee231f640 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -1,4 +1,3 @@ - #include "viewmanager.hpp" #include diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index bc7f9b5a1..657a47750 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -1,4 +1,3 @@ - #include "editwidget.hpp" #include diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index 7a42ef0a5..c6c6cc6cc 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -1,4 +1,3 @@ - #include "filterbox.hpp" #include diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index 97490d508..2bf589215 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -1,4 +1,3 @@ - #include "recordfilterbox.hpp" #include diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 99658e1c8..156d9728b 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -1,4 +1,3 @@ - #include "cell.hpp" #include diff --git a/apps/opencs/view/render/editmode.cpp b/apps/opencs/view/render/editmode.cpp index 9361030a3..8a99ba049 100644 --- a/apps/opencs/view/render/editmode.cpp +++ b/apps/opencs/view/render/editmode.cpp @@ -1,4 +1,3 @@ - #include "editmode.hpp" #include "worldspacewidget.hpp" diff --git a/apps/opencs/view/render/lightingbright.cpp b/apps/opencs/view/render/lightingbright.cpp index a342ab093..41d7c5c65 100644 --- a/apps/opencs/view/render/lightingbright.cpp +++ b/apps/opencs/view/render/lightingbright.cpp @@ -1,4 +1,3 @@ - #include "lightingbright.hpp" #include diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 21fba4d4e..bcaadbcea 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -1,4 +1,3 @@ - #include "pagedworldspacewidget.hpp" #include diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index da18e7c89..c00c56ed0 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -1,4 +1,3 @@ - #include "previewwidget.hpp" #include diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 383382938..4f9dbb96c 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -1,4 +1,3 @@ - #include "unpagedworldspacewidget.hpp" #include diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index ba0ec8446..823a38c80 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -1,4 +1,3 @@ - #include "worldspacewidget.hpp" #include diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index e29447f25..a7316359e 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -1,4 +1,3 @@ - #include "reportsubview.hpp" #include "reporttable.hpp" diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index 550c53969..d4cecc971 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -1,4 +1,3 @@ - #include "reporttable.hpp" #include diff --git a/apps/opencs/view/tools/searchbox.cpp b/apps/opencs/view/tools/searchbox.cpp index 1307c1aab..d98044760 100644 --- a/apps/opencs/view/tools/searchbox.cpp +++ b/apps/opencs/view/tools/searchbox.cpp @@ -1,4 +1,3 @@ - #include "searchbox.hpp" #include diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index 8b35db6ae..d3fdbbf5d 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -1,4 +1,3 @@ - #include "searchsubview.hpp" #include diff --git a/apps/opencs/view/tools/subviews.cpp b/apps/opencs/view/tools/subviews.cpp index 8a343ebe8..8c3d6d50e 100644 --- a/apps/opencs/view/tools/subviews.cpp +++ b/apps/opencs/view/tools/subviews.cpp @@ -1,4 +1,3 @@ - #include "subviews.hpp" #include "../doc/subviewfactoryimp.hpp" diff --git a/apps/opencs/view/widget/modebutton.cpp b/apps/opencs/view/widget/modebutton.cpp index 56896b422..7c62f6bb1 100644 --- a/apps/opencs/view/widget/modebutton.cpp +++ b/apps/opencs/view/widget/modebutton.cpp @@ -1,4 +1,3 @@ - #include "modebutton.hpp" CSVWidget::ModeButton::ModeButton (const QIcon& icon, const QString& tooltip, QWidget *parent) diff --git a/apps/opencs/view/widget/pushbutton.cpp b/apps/opencs/view/widget/pushbutton.cpp index 1baeb7ca2..424aaf68a 100644 --- a/apps/opencs/view/widget/pushbutton.cpp +++ b/apps/opencs/view/widget/pushbutton.cpp @@ -1,4 +1,3 @@ - #include "pushbutton.hpp" #include diff --git a/apps/opencs/view/widget/scenetool.cpp b/apps/opencs/view/widget/scenetool.cpp index b8e9f895f..796b98567 100644 --- a/apps/opencs/view/widget/scenetool.cpp +++ b/apps/opencs/view/widget/scenetool.cpp @@ -1,4 +1,3 @@ - #include "scenetool.hpp" #include diff --git a/apps/opencs/view/widget/scenetoolbar.cpp b/apps/opencs/view/widget/scenetoolbar.cpp index f7023b31f..b2e988dc9 100644 --- a/apps/opencs/view/widget/scenetoolbar.cpp +++ b/apps/opencs/view/widget/scenetoolbar.cpp @@ -1,4 +1,3 @@ - #include "scenetoolbar.hpp" #include diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index 39e051c48..9f963873c 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -1,4 +1,3 @@ - #include "scenetoolmode.hpp" #include diff --git a/apps/opencs/view/widget/scenetoolrun.cpp b/apps/opencs/view/widget/scenetoolrun.cpp index 4c9eb676e..1e2d44e7a 100644 --- a/apps/opencs/view/widget/scenetoolrun.cpp +++ b/apps/opencs/view/widget/scenetoolrun.cpp @@ -1,4 +1,3 @@ - #include "scenetoolrun.hpp" #include diff --git a/apps/opencs/view/widget/scenetooltoggle.cpp b/apps/opencs/view/widget/scenetooltoggle.cpp index 07c448e45..d7251882a 100644 --- a/apps/opencs/view/widget/scenetooltoggle.cpp +++ b/apps/opencs/view/widget/scenetooltoggle.cpp @@ -1,4 +1,3 @@ - #include "scenetooltoggle.hpp" #include diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index 313e519cb..e0431476e 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -1,4 +1,3 @@ - #include "scenetooltoggle2.hpp" #include diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index c7d909f4c..2a710a940 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -1,4 +1,3 @@ - #include "cellcreator.hpp" #include diff --git a/apps/opencs/view/world/creator.cpp b/apps/opencs/view/world/creator.cpp index 7a8c8d48f..7a93339c5 100644 --- a/apps/opencs/view/world/creator.cpp +++ b/apps/opencs/view/world/creator.cpp @@ -1,4 +1,3 @@ - #include "creator.hpp" #include diff --git a/apps/opencs/view/world/dialoguecreator.cpp b/apps/opencs/view/world/dialoguecreator.cpp index 3d451ed2d..7c6fb2e81 100644 --- a/apps/opencs/view/world/dialoguecreator.cpp +++ b/apps/opencs/view/world/dialoguecreator.cpp @@ -1,4 +1,3 @@ - #include "dialoguecreator.hpp" #include diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 2190a62c6..e582e3356 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -1,4 +1,3 @@ - #include "enumdelegate.hpp" #include diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index 5f04d9a7a..8ed52bf80 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -1,4 +1,3 @@ - #include "genericcreator.hpp" #include diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index 13b05d2d1..1092d7217 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -1,4 +1,3 @@ - #include "idvalidator.hpp" #include diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index 268a82a28..1139afd69 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -1,4 +1,3 @@ - #include "infocreator.hpp" #include diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index 756e79fe6..f3312bb20 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -1,4 +1,3 @@ - #include "previewsubview.hpp" #include diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp index 9cae0d0c9..1a838a3b3 100644 --- a/apps/opencs/view/world/recordbuttonbar.cpp +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -1,4 +1,3 @@ - #include "recordbuttonbar.hpp" #include diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index e8055ed31..1357ca46f 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -1,4 +1,3 @@ - #include "referenceablecreator.hpp" #include diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp index 2b0d44df0..73ca62e02 100644 --- a/apps/opencs/view/world/referencecreator.cpp +++ b/apps/opencs/view/world/referencecreator.cpp @@ -1,4 +1,3 @@ - #include "referencecreator.hpp" #include diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index bc96b0952..49764bd17 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -1,4 +1,3 @@ - #include "regionmap.hpp" #include diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp index 411e24e75..996d1dc8b 100644 --- a/apps/opencs/view/world/regionmapsubview.cpp +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -1,4 +1,3 @@ - #include "regionmapsubview.hpp" #include "regionmap.hpp" diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index b7a795e23..2ca2548c0 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -1,4 +1,3 @@ - #include "scenesubview.hpp" #include diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index 415e6c9dc..b44e1c0bd 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -1,4 +1,3 @@ - #include "scripterrortable.hpp" #include diff --git a/apps/opencs/view/world/scripthighlighter.cpp b/apps/opencs/view/world/scripthighlighter.cpp index 4923a44d8..487b5b139 100644 --- a/apps/opencs/view/world/scripthighlighter.cpp +++ b/apps/opencs/view/world/scripthighlighter.cpp @@ -1,4 +1,3 @@ - #include "scripthighlighter.hpp" #include diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 73000af13..47a109335 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -1,4 +1,3 @@ - #include "subviews.hpp" #include "../doc/subviewfactoryimp.hpp" diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index b4b8c402a..f43cb0c06 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -1,4 +1,3 @@ - #include "table.hpp" #include diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index d22bcde4d..00d563607 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -1,4 +1,3 @@ - #include "tablebottombox.hpp" #include diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index f20d79036..d00a6f836 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -1,4 +1,3 @@ - #include "tablesubview.hpp" #include diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 8d83ab567..02e00fef1 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -1,4 +1,3 @@ - #include "util.hpp" #include diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp index 90a686a67..a63e6028c 100644 --- a/apps/opencs/view/world/vartypedelegate.cpp +++ b/apps/opencs/view/world/vartypedelegate.cpp @@ -1,4 +1,3 @@ - #include "vartypedelegate.hpp" #include diff --git a/apps/openmw/android_commandLine.h b/apps/openmw/android_commandLine.h index 21d1064c6..5ca79c2d0 100644 --- a/apps/openmw/android_commandLine.h +++ b/apps/openmw/android_commandLine.h @@ -1,5 +1,4 @@ - /* DO NOT EDIT THIS FILE - it is machine generated */ #include #ifndef _Included_ui_activity_GameActivity_commandLine diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index a90eec5bf..4efa7c273 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -1,4 +1,3 @@ - #include "environment.hpp" #include diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 457b0cec1..d8a0c8091 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -1,4 +1,3 @@ - #include "activator.hpp" #include diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 2abd071bd..3add529a9 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -1,4 +1,3 @@ - #include "apparatus.hpp" #include diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 686f5af61..7a3c395e4 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -1,4 +1,3 @@ - #include "armor.hpp" #include diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index e9538a6cb..c303b23af 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -1,4 +1,3 @@ - #include "classes.hpp" #include "activator.hpp" diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index b387a3e9f..488142e72 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -1,4 +1,3 @@ - #include "clothing.hpp" #include diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 5f49a74b6..9d2e34ea3 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -1,4 +1,3 @@ - #include "container.hpp" #include diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 192bdf2ce..49291be2d 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -1,4 +1,3 @@ - #include "creature.hpp" #include diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index dbc4b6af7..433e5fcea 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -1,4 +1,3 @@ - #include "creaturelevlist.hpp" #include diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 48fc3b64c..ab9cfa289 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -1,4 +1,3 @@ - #include "door.hpp" #include diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index de43e818e..6388c9f9c 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -1,4 +1,3 @@ - #include "ingredient.hpp" #include diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp index d31080bb2..a70f31115 100644 --- a/apps/openmw/mwclass/itemlevlist.cpp +++ b/apps/openmw/mwclass/itemlevlist.cpp @@ -1,4 +1,3 @@ - #include "itemlevlist.hpp" #include diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 90c708f97..9a80c7036 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -1,4 +1,3 @@ - #include "light.hpp" #include diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 478c50301..2b6a217e6 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -1,4 +1,3 @@ - #include "lockpick.hpp" #include diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index f5daafeec..7f1f2c5a8 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -1,4 +1,3 @@ - #include "misc.hpp" #include diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 3b0234a25..746099ae5 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1,4 +1,3 @@ - #include "npc.hpp" #include diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index ee299ab4f..5e1849a72 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -1,4 +1,3 @@ - #include "potion.hpp" #include diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index da22e9be6..00d79653d 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -1,4 +1,3 @@ - #include "probe.hpp" #include diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index c02146f12..403eb611b 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -1,4 +1,3 @@ - #include "repair.hpp" #include diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index dbbe7e43a..7bfd4c76d 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -1,4 +1,3 @@ - #include "static.hpp" #include diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index a484ad668..3e39c305a 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -1,4 +1,3 @@ - #include "weapon.hpp" #include diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 1785575fc..982f32956 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -1,4 +1,3 @@ - #include "dialoguemanagerimp.hpp" #include diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index adb7d3892..35ccc287c 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -1,4 +1,3 @@ - #include "filter.hpp" #include diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 9f07f7b6f..2f5f02b01 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -1,4 +1,3 @@ - #include "journalentry.hpp" #include diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 99dab0cf8..e6ffe22ab 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -1,4 +1,3 @@ - #include "journalimp.hpp" #include diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index a9e39b379..846597886 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -1,4 +1,3 @@ - #include "quest.hpp" #include diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index fa0fbfe13..a4eba30ae 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -1,4 +1,3 @@ - #include "selectwrapper.hpp" #include diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index c1a45f841..eb7fbdc1d 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -1,4 +1,3 @@ - #include "topic.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index c50d47ef5..cf5bb14a2 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -1,4 +1,3 @@ - #ifndef MWGUI_TOOLTIPS_H #define MWGUI_TOOLTIPS_H diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 2fc9fe42f..42dd3048f 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -1,4 +1,3 @@ - #include "alchemy.hpp" #include diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 0b19df0a8..021691d6a 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -1,4 +1,3 @@ - #include "magiceffects.hpp" #include diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index b9aa8b301..10d603ff1 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -1,4 +1,3 @@ - #include "npcstats.hpp" #include diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index b1829964b..4636ecfae 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -1,4 +1,3 @@ - #include "spells.hpp" #include diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index f0cb8a967..c4228884c 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -1,4 +1,3 @@ - #include "aiextensions.hpp" #include diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index c43cdf565..07a8a300a 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -1,4 +1,3 @@ - #include "animationextensions.hpp" #include diff --git a/apps/openmw/mwscript/compilercontext.cpp b/apps/openmw/mwscript/compilercontext.cpp index 3ff75eeae..083f463bc 100644 --- a/apps/openmw/mwscript/compilercontext.cpp +++ b/apps/openmw/mwscript/compilercontext.cpp @@ -1,4 +1,3 @@ - #include "compilercontext.hpp" #include "../mwworld/esmstore.hpp" diff --git a/apps/openmw/mwscript/consoleextensions.cpp b/apps/openmw/mwscript/consoleextensions.cpp index 30956d429..d2e07d3e1 100644 --- a/apps/openmw/mwscript/consoleextensions.cpp +++ b/apps/openmw/mwscript/consoleextensions.cpp @@ -1,4 +1,3 @@ - #include "consoleextensions.hpp" #include diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 70475d8e2..c456032a4 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -1,4 +1,3 @@ - #include "containerextensions.hpp" #include diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 904e0ee85..626fafb5a 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -1,4 +1,3 @@ - #include "controlextensions.hpp" #include diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 8b6805264..c305fb81f 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -1,4 +1,3 @@ - #include "dialogueextensions.hpp" #include diff --git a/apps/openmw/mwscript/extensions.cpp b/apps/openmw/mwscript/extensions.cpp index 2170ba4fb..12bf3413a 100644 --- a/apps/openmw/mwscript/extensions.cpp +++ b/apps/openmw/mwscript/extensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index a6ad2cc11..e0c78fc6d 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -1,4 +1,3 @@ - #include "globalscripts.hpp" #include diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 40c555f50..f48360c04 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -1,4 +1,3 @@ - #include "guiextensions.hpp" #include diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index df675aebb..b0d4d3f2d 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -1,4 +1,3 @@ - #include "interpretercontext.hpp" #include diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 38106340a..5941cba0d 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1,4 +1,3 @@ - #include "miscextensions.hpp" #include diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 5f755e542..084beb812 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -1,4 +1,3 @@ - #include "scriptmanagerimp.hpp" #include diff --git a/apps/openmw/mwscript/skyextensions.cpp b/apps/openmw/mwscript/skyextensions.cpp index d28d01b63..e0fccd16d 100644 --- a/apps/openmw/mwscript/skyextensions.cpp +++ b/apps/openmw/mwscript/skyextensions.cpp @@ -1,4 +1,3 @@ - #include "skyextensions.hpp" #include diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 606de7aa0..a9896d203 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/apps/openmw/mwscript/userextensions.cpp b/apps/openmw/mwscript/userextensions.cpp index 538133c65..165a93062 100644 --- a/apps/openmw/mwscript/userextensions.cpp +++ b/apps/openmw/mwscript/userextensions.cpp @@ -1,4 +1,3 @@ - #include "userextensions.hpp" #include diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index f190565da..f8eb0410c 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -1,4 +1,3 @@ - #include "character.hpp" #include diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index 70e9f0925..22192c355 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -1,4 +1,3 @@ - #include "charactermanager.hpp" #include diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 0883bc63b..83f1d7406 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -1,4 +1,3 @@ - #include "statemanagerimp.hpp" #include diff --git a/apps/openmw/mwworld/action.cpp b/apps/openmw/mwworld/action.cpp index 5e1fb41a6..fb2059de9 100644 --- a/apps/openmw/mwworld/action.cpp +++ b/apps/openmw/mwworld/action.cpp @@ -1,4 +1,3 @@ - #include "action.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp index bfd64c85d..00c9628ce 100644 --- a/apps/openmw/mwworld/actionapply.cpp +++ b/apps/openmw/mwworld/actionapply.cpp @@ -1,4 +1,3 @@ - #include "actionapply.hpp" #include "class.hpp" diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 660915523..7ca7dcbfd 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -1,4 +1,3 @@ - #include "actioneat.hpp" #include diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp index 8578995ae..454cc09f1 100644 --- a/apps/openmw/mwworld/actionopen.hpp +++ b/apps/openmw/mwworld/actionopen.hpp @@ -1,4 +1,3 @@ - #ifndef GAME_MWWORLD_ACTIONOPEN_H #define GAME_MWWORLD_ACTIONOPEN_H diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index 269d941dc..4e6135764 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -1,4 +1,3 @@ - #include "actiontake.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp index 905497f85..051380ff5 100644 --- a/apps/openmw/mwworld/actiontalk.cpp +++ b/apps/openmw/mwworld/actiontalk.cpp @@ -1,4 +1,3 @@ - #include "actiontalk.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 6fa9ba9b6..98d30ad24 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -1,4 +1,3 @@ - #include "class.hpp" #include diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d4aadc6c7..bf3732a3a 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -1,4 +1,3 @@ - #include "containerstore.hpp" #include diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index e7cb04590..dcd7924a2 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -1,4 +1,3 @@ - #include "globals.hpp" #include diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index a2e445d58..5b0c2311a 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -1,4 +1,3 @@ - #include "inventorystore.hpp" #include diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 1cf22744a..4d74c3c58 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -1,4 +1,3 @@ - #include "ptr.hpp" #include diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index ae985f857..15ac52811 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -1,4 +1,3 @@ - #include "refdata.hpp" #include diff --git a/components/compiler/controlparser.cpp b/components/compiler/controlparser.cpp index aefe6d16d..b202467db 100644 --- a/components/compiler/controlparser.cpp +++ b/components/compiler/controlparser.cpp @@ -1,4 +1,3 @@ - #include "controlparser.hpp" #include diff --git a/components/compiler/declarationparser.cpp b/components/compiler/declarationparser.cpp index 7961b8f41..ffac252d5 100644 --- a/components/compiler/declarationparser.cpp +++ b/components/compiler/declarationparser.cpp @@ -1,4 +1,3 @@ - #include "declarationparser.hpp" #include diff --git a/components/compiler/discardparser.cpp b/components/compiler/discardparser.cpp index 6028968bb..da114fb3d 100644 --- a/components/compiler/discardparser.cpp +++ b/components/compiler/discardparser.cpp @@ -1,4 +1,3 @@ - #include "discardparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/errorhandler.cpp b/components/compiler/errorhandler.cpp index bcd30ef2d..a987a86da 100644 --- a/components/compiler/errorhandler.cpp +++ b/components/compiler/errorhandler.cpp @@ -1,4 +1,3 @@ - #include "errorhandler.hpp" namespace Compiler diff --git a/components/compiler/errorhandler.hpp b/components/compiler/errorhandler.hpp index c92e7bb8d..ea904e385 100644 --- a/components/compiler/errorhandler.hpp +++ b/components/compiler/errorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_ERRORHANDLER_H_INCLUDED #define COMPILER_ERRORHANDLER_H_INCLUDED diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 1818d04df..b588b6196 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -1,4 +1,3 @@ - #include "exprparser.hpp" #include diff --git a/components/compiler/extensions.cpp b/components/compiler/extensions.cpp index c2b11c615..dbb953e20 100644 --- a/components/compiler/extensions.cpp +++ b/components/compiler/extensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/components/compiler/generator.cpp b/components/compiler/generator.cpp index ead0c7290..7e6437e20 100644 --- a/components/compiler/generator.cpp +++ b/components/compiler/generator.cpp @@ -1,4 +1,3 @@ - #include "generator.hpp" #include diff --git a/components/compiler/junkparser.cpp b/components/compiler/junkparser.cpp index cfa94044e..7608e9bab 100644 --- a/components/compiler/junkparser.cpp +++ b/components/compiler/junkparser.cpp @@ -1,4 +1,3 @@ - #include "junkparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 4d6e147fc..032af7d65 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -1,4 +1,3 @@ - #include "lineparser.hpp" #include diff --git a/components/compiler/literals.cpp b/components/compiler/literals.cpp index 626b03afb..ee2c4d345 100644 --- a/components/compiler/literals.cpp +++ b/components/compiler/literals.cpp @@ -1,4 +1,3 @@ - #include "literals.hpp" #include diff --git a/components/compiler/locals.cpp b/components/compiler/locals.cpp index 60a5704bf..768fc077c 100644 --- a/components/compiler/locals.cpp +++ b/components/compiler/locals.cpp @@ -1,4 +1,3 @@ - #include "locals.hpp" #include diff --git a/components/compiler/nullerrorhandler.cpp b/components/compiler/nullerrorhandler.cpp index ee2884705..a0db53a00 100644 --- a/components/compiler/nullerrorhandler.cpp +++ b/components/compiler/nullerrorhandler.cpp @@ -1,4 +1,3 @@ - #include "nullerrorhandler.hpp" void Compiler::NullErrorHandler::report (const std::string& message, const TokenLoc& loc, Type type) {} diff --git a/components/compiler/nullerrorhandler.hpp b/components/compiler/nullerrorhandler.hpp index bb4db99a2..3dcff9250 100644 --- a/components/compiler/nullerrorhandler.hpp +++ b/components/compiler/nullerrorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_NULLERRORHANDLER_H_INCLUDED #define COMPILER_NULLERRORHANDLER_H_INCLUDED diff --git a/components/compiler/output.cpp b/components/compiler/output.cpp index 46e04b8dc..785b2ce84 100644 --- a/components/compiler/output.cpp +++ b/components/compiler/output.cpp @@ -1,4 +1,3 @@ - #include "output.hpp" #include diff --git a/components/compiler/parser.cpp b/components/compiler/parser.cpp index 0f442c350..fe019718a 100644 --- a/components/compiler/parser.cpp +++ b/components/compiler/parser.cpp @@ -1,4 +1,3 @@ - #include "parser.hpp" #include diff --git a/components/compiler/quickfileparser.cpp b/components/compiler/quickfileparser.cpp index 4e9f76e13..53aaf96e5 100644 --- a/components/compiler/quickfileparser.cpp +++ b/components/compiler/quickfileparser.cpp @@ -1,4 +1,3 @@ - #include "quickfileparser.hpp" #include "skipparser.hpp" diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index de7f7e1e1..5af396d27 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -1,4 +1,3 @@ - #include "scanner.hpp" #include diff --git a/components/compiler/scriptparser.cpp b/components/compiler/scriptparser.cpp index ea11be5f0..a3bf23288 100644 --- a/components/compiler/scriptparser.cpp +++ b/components/compiler/scriptparser.cpp @@ -1,4 +1,3 @@ - #include "scriptparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/skipparser.cpp b/components/compiler/skipparser.cpp index c7cb31f58..3e704253d 100644 --- a/components/compiler/skipparser.cpp +++ b/components/compiler/skipparser.cpp @@ -1,4 +1,3 @@ - #include "skipparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/streamerrorhandler.cpp b/components/compiler/streamerrorhandler.cpp index fc1a05943..9ca8aa74b 100644 --- a/components/compiler/streamerrorhandler.cpp +++ b/components/compiler/streamerrorhandler.cpp @@ -1,4 +1,3 @@ - #include "streamerrorhandler.hpp" #include "tokenloc.hpp" diff --git a/components/compiler/streamerrorhandler.hpp b/components/compiler/streamerrorhandler.hpp index 96e02b588..85de1833a 100644 --- a/components/compiler/streamerrorhandler.hpp +++ b/components/compiler/streamerrorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_STREAMERRORHANDLER_H_INCLUDED #define COMPILER_STREAMERRORHANDLER_H_INCLUDED diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index 7a2098fb3..f8798eccd 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -1,4 +1,3 @@ - #include "stringparser.hpp" #include diff --git a/components/esm/cellid.cpp b/components/esm/cellid.cpp index 3c6e23ffd..5ac8c4cab 100644 --- a/components/esm/cellid.cpp +++ b/components/esm/cellid.cpp @@ -1,4 +1,3 @@ - #include "cellid.hpp" #include "esmreader.hpp" diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index c3b889df5..33ac4a91e 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -1,4 +1,3 @@ - #include "cellref.hpp" #include "esmreader.hpp" diff --git a/components/esm/cellstate.cpp b/components/esm/cellstate.cpp index 4df04d0e5..83b130dcd 100644 --- a/components/esm/cellstate.cpp +++ b/components/esm/cellstate.cpp @@ -1,4 +1,3 @@ - #include "cellstate.hpp" #include "esmreader.hpp" diff --git a/components/esm/containerstate.cpp b/components/esm/containerstate.cpp index 80ad5cbdc..301549d59 100644 --- a/components/esm/containerstate.cpp +++ b/components/esm/containerstate.cpp @@ -1,4 +1,3 @@ - #include "containerstate.hpp" void ESM::ContainerState::load (ESMReader &esm) diff --git a/components/esm/creaturestate.cpp b/components/esm/creaturestate.cpp index c15becd98..bffa4e5e4 100644 --- a/components/esm/creaturestate.cpp +++ b/components/esm/creaturestate.cpp @@ -1,4 +1,3 @@ - #include "creaturestate.hpp" void ESM::CreatureState::load (ESMReader &esm) diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 6c05fac2a..9d605a6af 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -1,4 +1,3 @@ - #include "debugprofile.hpp" #include "esmreader.hpp" diff --git a/components/esm/dialoguestate.cpp b/components/esm/dialoguestate.cpp index f302e36dc..2b1887e4e 100644 --- a/components/esm/dialoguestate.cpp +++ b/components/esm/dialoguestate.cpp @@ -1,4 +1,3 @@ - #include "dialoguestate.hpp" #include "esmreader.hpp" diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index a80427bbe..5bc768f72 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -1,4 +1,3 @@ - #include "filter.hpp" #include "esmreader.hpp" diff --git a/components/esm/globalscript.cpp b/components/esm/globalscript.cpp index 0129f8eb7..a42cdc230 100644 --- a/components/esm/globalscript.cpp +++ b/components/esm/globalscript.cpp @@ -1,4 +1,3 @@ - #include "globalscript.hpp" #include "esmreader.hpp" diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index 4eaaa9f9f..e7257ae53 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -1,4 +1,3 @@ - #include "inventorystate.hpp" #include "esmreader.hpp" diff --git a/components/esm/journalentry.cpp b/components/esm/journalentry.cpp index 445213de4..93011e581 100644 --- a/components/esm/journalentry.cpp +++ b/components/esm/journalentry.cpp @@ -1,4 +1,3 @@ - #include "journalentry.hpp" #include "esmreader.hpp" diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index 7d749c4d9..df35a2579 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -1,4 +1,3 @@ - #include "loadtes3.hpp" #include "esmcommon.hpp" diff --git a/components/esm/locals.cpp b/components/esm/locals.cpp index f0cfd49f0..bd51be08f 100644 --- a/components/esm/locals.cpp +++ b/components/esm/locals.cpp @@ -1,4 +1,3 @@ - #include "locals.hpp" #include "esmreader.hpp" diff --git a/components/esm/npcstate.cpp b/components/esm/npcstate.cpp index 724d67326..6c9988d50 100644 --- a/components/esm/npcstate.cpp +++ b/components/esm/npcstate.cpp @@ -1,4 +1,3 @@ - #include "npcstate.hpp" void ESM::NpcState::load (ESMReader &esm) diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 9ef1ccf80..3b5288c77 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -1,4 +1,3 @@ - #include "objectstate.hpp" #include "esmreader.hpp" diff --git a/components/esm/player.cpp b/components/esm/player.cpp index e64cefc30..9ec53240a 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -1,4 +1,3 @@ - #include "player.hpp" #include "esmreader.hpp" diff --git a/components/esm/queststate.cpp b/components/esm/queststate.cpp index c8cff7adc..5408cd2ff 100644 --- a/components/esm/queststate.cpp +++ b/components/esm/queststate.cpp @@ -1,4 +1,3 @@ - #include "queststate.hpp" #include "esmreader.hpp" diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index b5e0810db..4211bb14e 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -1,4 +1,3 @@ - #include "savedgame.hpp" #include "esmreader.hpp" diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp index eeb0bf04f..aeea5017e 100644 --- a/components/esm/variantimp.cpp +++ b/components/esm/variantimp.cpp @@ -1,4 +1,3 @@ - #include "variantimp.hpp" #include diff --git a/components/files/multidircollection.cpp b/components/files/multidircollection.cpp index 1abbae3ae..7b3b0c440 100644 --- a/components/files/multidircollection.cpp +++ b/components/files/multidircollection.cpp @@ -1,4 +1,3 @@ - #include "multidircollection.hpp" #include diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp index d705a109c..31e911f8b 100644 --- a/components/interpreter/installopcodes.cpp +++ b/components/interpreter/installopcodes.cpp @@ -1,4 +1,3 @@ - #include "installopcodes.hpp" #include diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp index ea1e9fc91..4fd3a8d8c 100644 --- a/components/interpreter/interpreter.cpp +++ b/components/interpreter/interpreter.cpp @@ -1,4 +1,3 @@ - #include "interpreter.hpp" #include diff --git a/components/interpreter/runtime.cpp b/components/interpreter/runtime.cpp index dc3da07a8..6599882f1 100644 --- a/components/interpreter/runtime.cpp +++ b/components/interpreter/runtime.cpp @@ -1,4 +1,3 @@ - #include "runtime.hpp" #include diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 13d1a9e1a..1833adcee 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -1,4 +1,3 @@ - # Copy resource files into the build directory set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIR ${OpenMW_BINARY_DIR}/resources/mygui) From d1601feb39f944ad62c13fa23f3a76eb05c7856d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 22 Aug 2015 12:57:39 +0200 Subject: [PATCH 160/441] Adjust OpenCS saving stages order to stop vanilla MW complaining about missing records --- apps/opencs/model/doc/saving.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 25372f1a6..95a2feaf2 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -80,22 +80,25 @@ CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& proje appendStage (new WriteCollectionStage > (mDocument.getData().getStartScripts(), mState)); - appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); - - appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); - appendStage (new WriteRefIdCollectionStage (mDocument, mState)); appendStage (new CollectionReferencesStage (mDocument, mState)); appendStage (new WriteCellCollectionStage (mDocument, mState)); - appendStage (new WritePathgridCollectionStage (mDocument, mState)); + // Dialogue can reference objects and cells so must be written after these records for vanilla-compatible files - appendStage (new WriteLandCollectionStage (mDocument, mState)); + appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); + + appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); + + appendStage (new WritePathgridCollectionStage (mDocument, mState)); appendStage (new WriteLandTextureCollectionStage (mDocument, mState)); + // references Land Textures + appendStage (new WriteLandCollectionStage (mDocument, mState)); + // close file and clean up appendStage (new CloseSaveStage (mState)); From 402f1ff5d892414ddbf17b2eb2e18f234ceb0230 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 22 Aug 2015 13:10:54 +0200 Subject: [PATCH 161/441] Fix the ESM::LandTexture NAME being discarded on loading --- apps/opencs/model/doc/savingstages.cpp | 2 ++ apps/opencs/model/world/landtexture.cpp | 8 +------- apps/opencs/model/world/landtexture.hpp | 7 ++----- apps/opencs/view/render/terrainstorage.cpp | 16 ++++++++++++---- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index ef3b23ec8..01d260d68 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -454,6 +454,8 @@ void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& mess mState.getWriter().startRecord (record.sRecordId); + mState.getWriter().writeHNString("NAME", record.mId); + record.save (mState.getWriter()); mState.getWriter().endRecord (record.sRecordId); diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index 4725866a5..e7772129c 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -9,13 +9,7 @@ namespace CSMWorld { ESM::LandTexture::load(esm); - int plugin = esm.getIndex(); - - std::ostringstream stream; - - stream << mIndex << "_" << plugin; - - mId = stream.str(); + mPluginIndex = esm.getIndex(); } } diff --git a/apps/opencs/model/world/landtexture.hpp b/apps/opencs/model/world/landtexture.hpp index b13903186..c0b6eeba9 100644 --- a/apps/opencs/model/world/landtexture.hpp +++ b/apps/opencs/model/world/landtexture.hpp @@ -7,13 +7,10 @@ namespace CSMWorld { - /// \brief Wrapper for LandTexture record. Encodes mIndex and the plugin index (obtained from ESMReader) - /// in the ID. - /// - /// \attention The mId field of the ESM::LandTexture struct is not used. + /// \brief Wrapper for LandTexture record, providing info which plugin the LandTexture was loaded from. struct LandTexture : public ESM::LandTexture { - std::string mId; + int mPluginIndex; void load (ESM::ESMReader &esm); }; diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index a14eea5dd..9998daeee 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -28,10 +28,18 @@ namespace CSVRender const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) { - std::ostringstream stream; - stream << index << "_" << plugin; - - return &mData.getLandTextures().getRecord(stream.str()).get(); + int numRecords = mData.getLandTextures().getSize(); + + for (int i=0; imIndex == index && ltex->mPluginIndex == plugin) + return ltex; + } + + std::stringstream error; + error << "Can't find LandTexture " << index << " from plugin " << plugin; + throw std::runtime_error(error.str()); } void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) From 29536751f52a44b966e95111edbb71917cbab3e2 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 21 Aug 2015 21:38:28 +0300 Subject: [PATCH 162/441] Sort tables by ID in the ascending order initially --- apps/opencs/view/world/table.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index f43cb0c06..9975e5355 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -276,12 +276,16 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, horizontalHeader()->setResizeMode (QHeaderView::Interactive); #endif verticalHeader()->hide(); - setSortingEnabled (sorting); setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); - int columns = mModel->columnCount(); + setSortingEnabled (sorting); + if (sorting) + { + sortByColumn (mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder); + } + int columns = mModel->columnCount(); for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); From 214f3e5b9bc96d16d9c293d9387220f22f061c94 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 21 Aug 2015 21:47:29 +0300 Subject: [PATCH 163/441] ModifyCommand uses a proper name of a modified nested value --- apps/opencs/model/world/commands.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 7673e6bc9..d510cd103 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -21,12 +21,17 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI // Replace proxy with actual model mIndex = proxy->mapToSource (index); mModel = proxy->sourceModel(); + } + if (mIndex.parent().isValid()) + { setText ("Modify " + dynamic_cast(mModel)->nestedHeaderData ( mIndex.parent().column(), mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); } else + { setText ("Modify " + mModel->headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); + } // Remember record state before the modification if (CSMWorld::IdTable *table = dynamic_cast(mModel)) From 8c07d7f252e7bb86ee0a061efe1021e701a0a1da Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 21 Aug 2015 22:05:40 +0300 Subject: [PATCH 164/441] Remove enum names for AiWanderRepeat column --- apps/opencs/model/world/columns.cpp | 6 ------ apps/opencs/view/doc/viewmanager.cpp | 1 - 2 files changed, 7 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index bc6d75a18..af26a6901 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -540,11 +540,6 @@ namespace "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 }; - static const char *sAiWanderRepeat[] = - { - "No", "Yes", 0 - }; - static const char *sInfoCondFunc[] = { " ", "Function", "Global", "Local", "Journal", @@ -584,7 +579,6 @@ namespace case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; - case CSMWorld::Columns::ColumnId_AiWanderRepeat: return sAiWanderRepeat; case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc; // FIXME: don't have dynamic value enum delegate, use Display_String for now //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index ee231f640..8ffd7335d 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -102,7 +102,6 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_EffectId, CSMWorld::Columns::ColumnId_EffectId, false }, { CSMWorld::ColumnBase::Display_PartRefType, CSMWorld::Columns::ColumnId_PartRefType, false }, { CSMWorld::ColumnBase::Display_AiPackageType, CSMWorld::Columns::ColumnId_AiPackageType, false }, - { CSMWorld::ColumnBase::Display_Boolean, CSMWorld::Columns::ColumnId_AiWanderRepeat, false }, { CSMWorld::ColumnBase::Display_InfoCondFunc, CSMWorld::Columns::ColumnId_InfoCondFunc, false }, { CSMWorld::ColumnBase::Display_InfoCondComp, CSMWorld::Columns::ColumnId_InfoCondComp, false }, { CSMWorld::ColumnBase::Display_RaceSkill, CSMWorld::Columns::ColumnId_RaceSkill, true }, From 68013bd324db1e7cd1f7ee66d8331c09cd0f59f2 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 21 Aug 2015 22:36:53 +0300 Subject: [PATCH 165/441] Columns with Display_Boolean use Combobox editor even for non-boolean values --- apps/opencs/view/world/util.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 02e00fef1..1da9878ea 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "../../model/world/commands.hpp" #include "../../model/world/tablemimedata.hpp" @@ -173,7 +174,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO // TODO: Find a better solution? if (display == CSMWorld::ColumnBase::Display_Boolean) { - return QStyledItemDelegate::createEditor(parent, option, index); + return QItemEditorFactory::defaultFactory()->createEditor(QVariant::Bool, parent); } // For tables the pop-up of the color editor should appear immediately after the editor creation // (the third parameter of ColorEditor's constructor) From 3a41fe5024a37f80716bb6e509612a6020163dad Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Fri, 21 Aug 2015 22:55:58 +0300 Subject: [PATCH 166/441] Convert AiWanderRepeat to bool in ActorAiRefIdAdapter::getNestedData() --- apps/opencs/model/world/refidadapterimp.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 9fc296231..9d1c1907b 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1466,7 +1466,7 @@ namespace CSMWorld return QVariant(); case 5: // wander repeat if (content.mType == ESM::AI_Wander) - return content.mWander.mShouldRepeat; + return content.mWander.mShouldRepeat != 0; else return QVariant(); case 6: // activate name From b53a77389ba490c327b40da576375310c72533ea Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 22 Aug 2015 00:01:27 +0300 Subject: [PATCH 167/441] Show race only when mesh type is Skin (in BodyParts table) --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/columnimp.cpp | 28 +++++++++++++++++++++++++++ apps/opencs/model/world/columnimp.hpp | 19 +++++++++++++++--- apps/opencs/model/world/data.cpp | 7 +++++-- 4 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 apps/opencs/model/world/columnimp.cpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index db8e866df..ff6e0506c 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -23,7 +23,7 @@ opencs_units (model/world opencs_units_noqt (model/world - universalid record commands columnbase scriptcontext cell refidcollection + universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection idcompletionmanager npcstats metadata diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp new file mode 100644 index 000000000..dc3d39edb --- /dev/null +++ b/apps/opencs/model/world/columnimp.cpp @@ -0,0 +1,28 @@ +#include "columnimp.hpp" + +CSMWorld::BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn *meshType) + : mMeshType(meshType) +{} + +QVariant CSMWorld::BodyPartRaceColumn::get(const Record &record) const +{ + if (mMeshType != NULL && mMeshType->get(record) == ESM::BodyPart::MT_Skin) + { + return QString::fromUtf8(record.get().mRace.c_str()); + } + return QVariant(QVariant::UserType); +} + +void CSMWorld::BodyPartRaceColumn::set(Record &record, const QVariant &data) +{ + ESM::BodyPart record2 = record.get(); + + record2.mRace = data.toString().toUtf8().constData(); + + record.setModified(record2); +} + +bool CSMWorld::BodyPartRaceColumn::isEditable() const +{ + return true; +} diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 15dd2c15b..824196c88 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -9,6 +9,8 @@ #include +#include + #include "columnbase.hpp" #include "columns.hpp" #include "info.hpp" @@ -1911,8 +1913,8 @@ namespace CSMWorld template struct MeshTypeColumn : public Column { - MeshTypeColumn() - : Column (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType) + MeshTypeColumn(int flags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) + : Column (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType, flags) {} virtual QVariant get (const Record& record) const @@ -2379,7 +2381,18 @@ namespace CSMWorld { return true; } - }; + }; + + struct BodyPartRaceColumn : public RaceColumn + { + const MeshTypeColumn *mMeshType; + + BodyPartRaceColumn(const MeshTypeColumn *meshType); + + virtual QVariant get(const Record &record) const; + virtual void set(Record &record, const QVariant &data); + virtual bool isEditable() const; + }; } #endif diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 80cdcb6b1..b6b7f689e 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -420,9 +420,12 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Female, ESM::BodyPart::BPF_Female)); mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Playable, ESM::BodyPart::BPF_NotPlayable, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true)); - mBodyParts.addColumn (new MeshTypeColumn); + + int meshTypeFlags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh; + MeshTypeColumn *meshTypeColumn = new MeshTypeColumn(meshTypeFlags); + mBodyParts.addColumn (meshTypeColumn); mBodyParts.addColumn (new ModelColumn); - mBodyParts.addColumn (new RaceColumn); + mBodyParts.addColumn (new BodyPartRaceColumn(meshTypeColumn)); mSoundGens.addColumn (new StringIdColumn); mSoundGens.addColumn (new RecordStateColumn); From 859ba937396e65cddc9926d9b63a0d8157195b78 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 22 Aug 2015 13:38:38 +0300 Subject: [PATCH 168/441] Inform about State change (not a whole row) when modifying a table value --- apps/opencs/model/world/idtable.cpp | 11 +++++++---- apps/opencs/model/world/idtree.cpp | 13 +++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index bdbc5e0c4..bd1179cea 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -76,12 +76,15 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole) { mIdCollection->setData (index.row(), index.column(), value); + emit dataChanged(index, index); // Modifying a value can also change the Modified status of a record. - // To track this, we inform about the change of a whole row. - QModelIndex rowStart = this->index(index.row(), 0); - QModelIndex rowEnd = this->index(index.row(), columnCount(index.parent()) - 1); - emit dataChanged(rowStart, rowEnd); + int stateColumn = searchColumnIndex(Columns::ColumnId_Modification); + if (stateColumn != -1) + { + QModelIndex stateIndex = this->index(index.row(), stateColumn); + emit dataChanged(stateIndex, stateIndex); + } return true; } diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 42ce72514..56d83d9ed 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -95,14 +95,15 @@ bool CSMWorld::IdTree::setData (const QModelIndex &index, const QVariant &value, const std::pair& parentAddress(unfoldIndexAddress(index.internalId())); mNestedCollection->setNestedData(parentAddress.first, parentAddress.second, value, index.row(), index.column()); - emit dataChanged(index, index); - // Modifying a value can also change the Modified status of a record (located in the parent row). - // To track this, we inform about the change of a whole parent row. - QModelIndex parentRowStart = this->index(index.parent().row(), 0); - QModelIndex parentRowEnd = this->index(index.parent().row(), columnCount(index.parent()) - 1); - emit dataChanged(parentRowStart, parentRowEnd); + // Modifying a value can also change the Modified status of a record. + int stateColumn = searchColumnIndex(Columns::ColumnId_Modification); + if (stateColumn != -1) + { + QModelIndex stateIndex = this->index(index.parent().row(), stateColumn); + emit dataChanged(stateIndex, stateIndex); + } return true; } From ca14d6a8a894a417da671c736fae67bed15d9d57 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 22 Aug 2015 14:34:05 +0300 Subject: [PATCH 169/441] Add missing includes to columnimp.hpp --- apps/opencs/model/world/columnimp.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 824196c88..4e608dbbd 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include "columnbase.hpp" #include "columns.hpp" From 843afc5e24559d2944a5e2113776826cdaf0089b Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 30 Jun 2015 03:54:56 +0200 Subject: [PATCH 170/441] Increment save file version and mark it as used, will be used in next commit (cherry picked from commit 4eaaa5e855d8a362b1385ade296835abf26f298c) --- apps/openmw/mwstate/statemanagerimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 83f1d7406..ac3ade4a9 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -203,8 +203,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.setFormat (ESM::Header::CurrentFormat); + writer.setVersion(1); + // all unused - writer.setVersion(0); writer.setType(0); writer.setAuthor(""); writer.setDescription(""); From b9ba1067bfb5306b05e544693a87fefbc38185de Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 30 Jun 2015 03:58:23 +0200 Subject: [PATCH 171/441] Savegame loading optimization (cherry picked from commit 463775060148a6b9a53676846a5ea31c4b38b423) --- apps/openmw/mwclass/creature.cpp | 30 ++++++++++++++---------------- apps/openmw/mwclass/npc.cpp | 19 +++++++++---------- components/esm/objectstate.cpp | 2 ++ components/esm/objectstate.hpp | 4 +++- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 49291be2d..13fd22a0c 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -807,27 +807,25 @@ namespace MWClass const ESM::CreatureState& state2 = dynamic_cast (state); - ensureCustomData(ptr); - - // If we do the following instead we get a sizable speedup, but this causes compatibility issues - // with 0.30 savegames, where some state in CreatureStats was not saved yet, - // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. - /* - if (!ptr.getRefData().getCustomData()) + if (state.mVersion > 0) { - // Create a CustomData, but don't fill it from ESM records (not needed) - std::auto_ptr data (new CreatureCustomData); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new CreatureCustomData); - MWWorld::LiveCellRef *ref = ptr.get(); + MWWorld::LiveCellRef *ref = ptr.get(); - if (ref->mBase->mFlags & ESM::Creature::Weapon) - data->mContainerStore = new MWWorld::InventoryStore(); - else - data->mContainerStore = new MWWorld::ContainerStore(); + if (ref->mBase->mFlags & ESM::Creature::Weapon) + data->mContainerStore = new MWWorld::InventoryStore(); + else + data->mContainerStore = new MWWorld::ContainerStore(); - ptr.getRefData().setCustomData (data.release()); + ptr.getRefData().setCustomData (data.release()); + } } - */ + else + ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 746099ae5..bc806078a 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1103,18 +1103,17 @@ namespace MWClass const ESM::NpcState& state2 = dynamic_cast (state); - ensureCustomData(ptr); - // If we do the following instead we get a sizable speedup, but this causes compatibility issues - // with 0.30 savegames, where some state in CreatureStats was not saved yet, - // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. - /* - if (!ptr.getRefData().getCustomData()) + if (state.mVersion > 0) { - // Create a CustomData, but don't fill it from ESM records (not needed) - std::auto_ptr data (new NpcCustomData); - ptr.getRefData().setCustomData (data.release()); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new NpcCustomData); + ptr.getRefData().setCustomData (data.release()); + } } - */ + else + ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 3b5288c77..f8c479ff6 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -5,6 +5,8 @@ void ESM::ObjectState::load (ESMReader &esm) { + mVersion = esm.getVer(); + mRef.loadData(esm); mHasLocals = 0; diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index d1077733a..674bcb8fc 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -29,7 +29,9 @@ namespace ESM // Is there any class-specific state following the ObjectState bool mHasCustomState; - ObjectState() : mHasCustomState(true) + unsigned int mVersion; + + ObjectState() : mHasCustomState(true), mVersion(0) {} /// @note Does not load the CellRef ID, it should already be loaded before calling this method From 05fd4e123e5f5d9f34c950564b5d08bfa27aaaed Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 30 Jun 2015 17:26:33 +0200 Subject: [PATCH 172/441] Use the format field instead of version field (cherry picked from commit a081d402c571c88ea69d8e6044a862f781fbf3f3) --- apps/openmw/mwstate/statemanagerimp.cpp | 5 ++--- components/esm/objectstate.cpp | 2 +- components/esm/savedgame.cpp | 1 + components/esm/savedgame.hpp | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index ac3ade4a9..cf2c079e3 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -201,11 +201,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot ++iter) writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 - writer.setFormat (ESM::Header::CurrentFormat); - - writer.setVersion(1); + writer.setFormat (ESM::SavedGame::sCurrentFormat); // all unused + writer.setVersion(0); writer.setType(0); writer.setAuthor(""); writer.setDescription(""); diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index f8c479ff6..62aa0452a 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -5,7 +5,7 @@ void ESM::ObjectState::load (ESMReader &esm) { - mVersion = esm.getVer(); + mVersion = esm.getFormat(); mRef.loadData(esm); diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 4211bb14e..2e5509b7a 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -5,6 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; +int ESM::SavedGame::sCurrentFormat = 1; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esm/savedgame.hpp b/components/esm/savedgame.hpp index 3e7cae775..aa0429657 100644 --- a/components/esm/savedgame.hpp +++ b/components/esm/savedgame.hpp @@ -15,6 +15,8 @@ namespace ESM { static unsigned int sRecordId; + static int sCurrentFormat; + struct TimeStamp { float mGameHour; From 65bd5d8492f864afc97296706f382cbf2a6885e6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 30 Jun 2015 17:26:42 +0200 Subject: [PATCH 173/441] Refuse loading save games of unknown format (cherry picked from commit 20d03c7e39ebf9f09570be0a5a3e1029a8001bd4) --- apps/openmw/mwstate/character.cpp | 3 --- apps/openmw/mwstate/statemanagerimp.cpp | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index f8eb0410c..733ac1171 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -28,9 +28,6 @@ void MWState::Character::addSlot (const boost::filesystem::path& path, const std ESM::ESMReader reader; reader.open (slot.mPath.string()); - if (reader.getFormat()>ESM::Header::CurrentFormat) - return; // format is too new -> ignore - if (reader.getRecName()!=ESM::REC_SAVE) return; // invalid save file -> ignore diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index cf2c079e3..3e8bc4d23 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -310,8 +310,6 @@ void MWState::StateManager::loadGame(const std::string& filepath) // have to peek into the save file to get the player name ESM::ESMReader reader; reader.open (filepath); - if (reader.getFormat()>ESM::Header::CurrentFormat) - return; // format is too new -> ignore if (reader.getRecName()!=ESM::REC_SAVE) return; // invalid save file -> ignore reader.getRecHeader(); @@ -333,6 +331,9 @@ void MWState::StateManager::loadGame (const Character *character, const std::str ESM::ESMReader reader; reader.open (filepath); + if (reader.getFormat() > ESM::SavedGame::sCurrentFormat) + throw std::runtime_error("This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade to the newest OpenMW version to load this file."); + std::map contentFileMap = buildContentFileIndexMap (reader); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); From 52dfc0e9c760cd87b67c165d6335a2e3ffcf793b Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 30 Jun 2015 17:43:22 +0200 Subject: [PATCH 174/441] Use the correct format specifier for ess-imported savegames (cherry picked from commit 2065e0fa2d700163bbf9bbeb8eec5e0ccf84976a) --- apps/essimporter/importer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 35b92a4d0..b9468ed87 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -296,7 +296,7 @@ namespace ESSImport ESM::ESMWriter writer; - writer.setFormat (ESM::Header::CurrentFormat); + writer.setFormat (ESM::SavedGame::sCurrentFormat); std::ofstream stream(mOutFile.c_str(), std::ios::binary); // all unused From 6fb658daa4b31c93643b34a96361b0033f821a52 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 30 Jul 2015 11:49:24 +0200 Subject: [PATCH 175/441] replaced State_Compiling (not requried anymore) with State_Merging (cherry picked from commit 6a9218ee07a81c9ca56faf8661c9d38e84ba3028) --- apps/opencs/model/doc/state.hpp | 2 +- apps/opencs/view/doc/operation.cpp | 3 ++- apps/opencs/view/doc/view.cpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/doc/state.hpp b/apps/opencs/model/doc/state.hpp index 78f468101..7352b4b99 100644 --- a/apps/opencs/model/doc/state.hpp +++ b/apps/opencs/model/doc/state.hpp @@ -12,7 +12,7 @@ namespace CSMDoc State_Saving = 16, State_Verifying = 32, - State_Compiling = 64, // not implemented yet + State_Merging = 64, State_Searching = 128, State_Loading = 256 // pseudo-state; can not be encountered in a loaded document }; diff --git a/apps/opencs/view/doc/operation.cpp b/apps/opencs/view/doc/operation.cpp index 95cbf012d..e5c1e7b89 100644 --- a/apps/opencs/view/doc/operation.cpp +++ b/apps/opencs/view/doc/operation.cpp @@ -19,6 +19,7 @@ void CSVDoc::Operation::updateLabel (int threads) case CSMDoc::State_Saving: name = "saving"; break; case CSMDoc::State_Verifying: name = "verifying"; break; case CSMDoc::State_Searching: name = "searching"; break; + case CSMDoc::State_Merging: name = "merging"; break; } std::ostringstream stream; @@ -122,7 +123,7 @@ void CSVDoc::Operation::setBarColor (int type) bottomColor = "#9ECB2D"; //green gloss break; - case CSMDoc::State_Compiling: + case CSMDoc::State_Merging: topColor = "#F3E2C7"; midTopColor = "#C19E67"; diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 173a78e9d..bf92ccf28 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -510,6 +510,7 @@ void CSVDoc::View::updateDocumentState() static const int operations[] = { CSMDoc::State_Saving, CSMDoc::State_Verifying, CSMDoc::State_Searching, + CSMDoc::State_Merging, -1 // end marker }; From ef1e01639e4459feb2261fb6ef38b6a5b09c6b34 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 6 Aug 2015 12:52:10 +0200 Subject: [PATCH 176/441] added UI for merge tool (merge tool itself is still missing) (cherry picked from commit b83f9445a96fda4dc14e43a2db363a748344227f) Conflicts: apps/opencs/editor.cpp apps/opencs/editor.hpp --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/editor.cpp | 16 +++ apps/opencs/editor.hpp | 12 +++ apps/opencs/model/doc/documentmanager.cpp | 2 + apps/opencs/model/doc/documentmanager.hpp | 2 + apps/opencs/view/doc/filewidget.cpp | 8 ++ apps/opencs/view/doc/filewidget.hpp | 4 + apps/opencs/view/doc/view.cpp | 11 ++ apps/opencs/view/doc/view.hpp | 5 + apps/opencs/view/doc/viewmanager.cpp | 1 + apps/opencs/view/doc/viewmanager.hpp | 2 + apps/opencs/view/tools/merge.cpp | 123 ++++++++++++++++++++++ apps/opencs/view/tools/merge.hpp | 60 +++++++++++ 13 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/view/tools/merge.cpp create mode 100644 apps/opencs/view/tools/merge.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index ff6e0506c..9cc9a2971 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -94,7 +94,7 @@ opencs_hdrs_noqt (view/render opencs_units (view/tools - reportsubview reporttable searchsubview searchbox + reportsubview reporttable searchsubview searchbox merge ) opencs_units_noqt (view/tools diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index bca21ff24..1de1e1865 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -48,9 +48,12 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) mNewGame.setLocalData (mLocal); mFileDialog.setLocalData (mLocal); + mMerge.setLocalData (mLocal); connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)), this, SLOT (documentAdded (CSMDoc::Document *))); + connect (&mDocumentManager, SIGNAL (documentAboutToBeRemoved (CSMDoc::Document *)), + this, SLOT (documentAboutToBeRemoved (CSMDoc::Document *))); connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()), this, SLOT (lastDocumentDeleted())); @@ -58,6 +61,7 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ())); connect (&mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ())); + connect (&mViewManager, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SLOT (mergeDocument (CSMDoc::Document *))); connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ())); connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ())); @@ -490,6 +494,12 @@ void CS::Editor::documentAdded (CSMDoc::Document *document) showSplashMessage(); } +void CS::Editor::documentAboutToBeRemoved (CSMDoc::Document *document) +{ + if (mMerge.getDocument()==document) + mMerge.cancel(); +} + void CS::Editor::lastDocumentDeleted() { QApplication::quit(); @@ -535,3 +545,9 @@ void CS::Editor::showSplashMessage() splash->raise(); // for X windows } } + +void CS::Editor::mergeDocument (CSMDoc::Document *document) +{ + mMerge.configure (document); + mMerge.show(); +} diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 040ca963f..33f5fd3b3 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -32,11 +32,18 @@ #include "view/settings/dialog.hpp" #include "view/render/overlaysystem.hpp" +#include "view/tools/merge.hpp" + namespace OgreInit { class OgreInit; } +namespace CSMDoc +{ + class Document; +} + namespace CS { class Editor : public QObject @@ -59,6 +66,7 @@ namespace CS boost::interprocess::file_lock mLock; boost::filesystem::ofstream mPidFile; bool mFsStrict; + CSVTools::Merge mMerge; void showSplashMessage(); @@ -103,8 +111,12 @@ namespace CS void documentAdded (CSMDoc::Document *document); + void documentAboutToBeRemoved (CSMDoc::Document *document); + void lastDocumentDeleted(); + void mergeDocument (CSMDoc::Document *document); + private: QString mIpcServerName; diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 070c15f9a..e8668d3ef 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -72,6 +72,8 @@ void CSMDoc::DocumentManager::removeDocument (CSMDoc::Document *document) if (iter==mDocuments.end()) throw std::runtime_error ("removing invalid document"); + emit documentAboutToBeRemoved (document); + mDocuments.erase (iter); document->deleteLater(); diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index f3fcbf8ec..5a892cc45 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -83,6 +83,8 @@ namespace CSMDoc void documentAdded (CSMDoc::Document *document); + void documentAboutToBeRemoved (CSMDoc::Document *document); + void loadRequest (CSMDoc::Document *document); void lastDocumentDeleted(); diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index bbb824823..110d561c1 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -55,3 +55,11 @@ void CSVDoc::FileWidget::extensionLabelIsVisible(bool visible) { mType->setVisible(visible); } + +void CSVDoc::FileWidget::setName (const std::string& text) +{ + QString text2 = QString::fromUtf8 (text.c_str()); + + mInput->setText (text2); + textChanged (text2); +} diff --git a/apps/opencs/view/doc/filewidget.hpp b/apps/opencs/view/doc/filewidget.hpp index ff09d71a3..ab06f37f1 100644 --- a/apps/opencs/view/doc/filewidget.hpp +++ b/apps/opencs/view/doc/filewidget.hpp @@ -3,6 +3,8 @@ #include +#include + class QLabel; class QString; class QLineEdit; @@ -29,6 +31,8 @@ namespace CSVDoc void extensionLabelIsVisible(bool visible); + void setName (const std::string& text); + private slots: void textChanged (const QString& text); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index bf92ccf28..9eb891c48 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -83,6 +83,10 @@ void CSVDoc::View::setupFileMenu() connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); file->addAction (mVerify); + mMerge = new QAction (tr ("Merge"), this); + connect (mMerge, SIGNAL (triggered()), this, SLOT (merge())); + file->addAction (mMerge); + QAction *loadErrors = new QAction (tr ("Load Error Log"), this); connect (loadErrors, SIGNAL (triggered()), this, SLOT (loadErrorLog())); file->addAction (loadErrors); @@ -418,6 +422,8 @@ void CSVDoc::View::updateActions() mGlobalDebugProfileMenu->updateActions (running); mStopDebug->setEnabled (running); + + mMerge->setEnabled (mDocument->getContentFiles().size()>1); } CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) @@ -1054,3 +1060,8 @@ void CSVDoc::View::updateScrollbar() else mSubViewWindow.setMinimumWidth(0); } + +void CSVDoc::View::merge() +{ + emit mergeDocument (mDocument); +} diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 7f465c8f3..04d31dea5 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -43,6 +43,7 @@ namespace CSVDoc QAction *mVerify; QAction *mShowStatusBar; QAction *mStopDebug; + QAction *mMerge; std::vector mEditingActions; Operations *mOperations; SubViewFactoryManager mSubViewFactory; @@ -137,6 +138,8 @@ namespace CSVDoc void editSettingsRequest(); + void mergeDocument (CSMDoc::Document *document); + public slots: void addSubView (const CSMWorld::UniversalId& id, const std::string& hint = ""); @@ -251,6 +254,8 @@ namespace CSVDoc void saveWindowState(); void moveScrollBarToEnd(int min, int max); + + void merge(); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 8ffd7335d..25949ef15 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -173,6 +173,7 @@ CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document) connect (view, SIGNAL (newAddonRequest ()), this, SIGNAL (newAddonRequest())); connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest())); connect (view, SIGNAL (editSettingsRequest()), this, SIGNAL (editSettingsRequest())); + connect (view, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SIGNAL (mergeDocument (CSMDoc::Document *))); connect (&CSMSettings::UserSettings::instance(), SIGNAL (userSettingUpdated(const QString &, const QStringList &)), diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp index cdd5ac768..70431107f 100644 --- a/apps/opencs/view/doc/viewmanager.hpp +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -77,6 +77,8 @@ namespace CSVDoc void editSettingsRequest(); + void mergeDocument (CSMDoc::Document *document); + public slots: void exitApplication (CSVDoc::View *view); diff --git a/apps/opencs/view/tools/merge.cpp b/apps/opencs/view/tools/merge.cpp new file mode 100644 index 000000000..baadd9179 --- /dev/null +++ b/apps/opencs/view/tools/merge.cpp @@ -0,0 +1,123 @@ + +#include "merge.hpp" + +#include +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" + +#include "../doc/filewidget.hpp" +#include "../doc/adjusterwidget.hpp" + +CSVTools::Merge::Merge (QWidget *parent) +: QDialog (parent), mDocument (0) +{ + setWindowTitle ("Merge Content Files into a new Game File"); + + QVBoxLayout *mainLayout = new QVBoxLayout; + setLayout (mainLayout); + + QSplitter *splitter = new QSplitter (Qt::Horizontal, this); + + mainLayout->addWidget (splitter, 1); + + // left panel (files to be merged) + QWidget *left = new QWidget (this); + left->setContentsMargins (0, 0, 0, 0); + splitter->addWidget (left); + + QVBoxLayout *leftLayout = new QVBoxLayout; + left->setLayout (leftLayout); + + leftLayout->addWidget (new QLabel ("Files to be merged", this)); + + mFiles = new QListWidget (this); + leftLayout->addWidget (mFiles, 1); + + // right panel (new game file) + QWidget *right = new QWidget (this); + right->setContentsMargins (0, 0, 0, 0); + splitter->addWidget (right); + + QVBoxLayout *rightLayout = new QVBoxLayout; + rightLayout->setAlignment (Qt::AlignTop); + right->setLayout (rightLayout); + + rightLayout->addWidget (new QLabel ("New game file", this)); + + mNewFile = new CSVDoc::FileWidget (this); + mNewFile->setType (false); + mNewFile->extensionLabelIsVisible (true); + rightLayout->addWidget (mNewFile); + + mAdjuster = new CSVDoc::AdjusterWidget (this); + + rightLayout->addWidget (mAdjuster); + + connect (mNewFile, SIGNAL (nameChanged (const QString&, bool)), + mAdjuster, SLOT (setName (const QString&, bool))); + connect (mAdjuster, SIGNAL (stateChanged (bool)), this, SLOT (stateChanged (bool))); + + // buttons + QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this); + + connect (buttons->button (QDialogButtonBox::Cancel), SIGNAL (clicked()), this, SLOT (reject())); + mOkay = new QPushButton ("Merge", this); + mOkay->setDefault (true); + buttons->addButton (mOkay, QDialogButtonBox::AcceptRole); + + mainLayout->addWidget (buttons); +} + +void CSVTools::Merge::configure (CSMDoc::Document *document) +{ + mDocument = document; + + mNewFile->setName (""); + + // content files + while (mFiles->count()) + delete mFiles->takeItem (0); + + std::vector files = document->getContentFiles(); + + for (std::vector::const_iterator iter (files.begin()); + iter!=files.end(); ++iter) + mFiles->addItem (QString::fromUtf8 (iter->filename().string().c_str())); +} + +void CSVTools::Merge::setLocalData (const boost::filesystem::path& localData) +{ + mAdjuster->setLocalData (localData); +} + +CSMDoc::Document *CSVTools::Merge::getDocument() const +{ + return mDocument; +} + +void CSVTools::Merge::cancel() +{ + mDocument = 0; + hide(); +} + +void CSVTools::Merge::accept() +{ + QDialog::accept(); +} + +void CSVTools::Merge::reject() +{ + QDialog::reject(); + cancel(); +} + +void CSVTools::Merge::stateChanged (bool valid) +{ + mOkay->setEnabled (valid); +} diff --git a/apps/opencs/view/tools/merge.hpp b/apps/opencs/view/tools/merge.hpp new file mode 100644 index 000000000..4538baebe --- /dev/null +++ b/apps/opencs/view/tools/merge.hpp @@ -0,0 +1,60 @@ +#ifndef CSV_TOOLS_REPORTTABLE_H +#define CSV_TOOLS_REPORTTABLE_H + +#include + +#include + +class QPushButton; +class QListWidget; + +namespace CSMDoc +{ + class Document; +} + +namespace CSVDoc +{ + class FileWidget; + class AdjusterWidget; +} + +namespace CSVTools +{ + class Merge : public QDialog + { + Q_OBJECT + + CSMDoc::Document *mDocument; + QPushButton *mOkay; + QListWidget *mFiles; + CSVDoc::FileWidget *mNewFile; + CSVDoc::AdjusterWidget *mAdjuster; + + public: + + Merge (QWidget *parent = 0); + + /// Configure dialogue for a new merge + void configure (CSMDoc::Document *document); + + void setLocalData (const boost::filesystem::path& localData); + + CSMDoc::Document *getDocument() const; + + void cancel(); + + public slots: + + virtual void accept(); + + virtual void reject(); + + private slots: + + void stateChanged (bool valid); + + }; +} + +#endif From a5a0990d3c4d91f8b8a4bb549906020a010f21c2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 6 Aug 2015 12:58:52 +0200 Subject: [PATCH 177/441] improved adjuster widget problem reporting (cherry picked from commit 4fd3097c1cf2329cef071aee7a1b149e280e4974) --- apps/opencs/view/doc/adjusterwidget.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index ba5dd90f8..620d853ac 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -63,6 +63,7 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) QString message; mValid = (!name.isEmpty()); + bool warning = false; if (!mValid) { @@ -105,13 +106,14 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) { /// \todo add an user setting to make this an error. message += "

    A file with the same name already exists. If you continue, it will be overwritten."; + warning = true; } } } mMessage->setText (message); mIcon->setPixmap (style()->standardIcon ( - mValid ? QStyle::SP_MessageBoxInformation : QStyle::SP_MessageBoxWarning). + mValid ? (warning ? QStyle::SP_MessageBoxWarning : QStyle::SP_MessageBoxInformation) : QStyle::SP_MessageBoxCritical). pixmap (QSize (16, 16))); emit stateChanged (mValid); From c2ed475950191349c789613e174834864cea8108 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 8 Aug 2015 16:47:58 +0200 Subject: [PATCH 178/441] disable merge menu item when a merge is already in progress (cherry picked from commit 708cacdec403793a0337c6e536a1ecf53a58bd6e) --- apps/opencs/view/doc/view.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 9eb891c48..47a2ffd51 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -423,7 +423,8 @@ void CSVDoc::View::updateActions() mGlobalDebugProfileMenu->updateActions (running); mStopDebug->setEnabled (running); - mMerge->setEnabled (mDocument->getContentFiles().size()>1); + mMerge->setEnabled (mDocument->getContentFiles().size()>1 && + !(mDocument->getState() & CSMDoc::State_Merging)); } CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) From e4381c3ec80008ccbff90590000e913bbbb7c0fe Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 13 Aug 2015 12:03:20 +0200 Subject: [PATCH 179/441] added merge operation (doesn't do anything yet) (cherry picked from commit 904ad949524864239f4b1f428d03a947360728a0) Conflicts: apps/opencs/CMakeLists.txt apps/opencs/model/tools/tools.cpp --- apps/opencs/CMakeLists.txt | 1 + apps/opencs/model/doc/document.cpp | 6 +++++ apps/opencs/model/doc/document.hpp | 5 ++-- apps/opencs/model/tools/mergeoperation.cpp | 15 +++++++++++ apps/opencs/model/tools/mergeoperation.hpp | 28 +++++++++++++++++++ apps/opencs/model/tools/tools.cpp | 31 +++++++++++++++++++--- apps/opencs/model/tools/tools.hpp | 11 ++++++-- apps/opencs/view/tools/merge.cpp | 8 ++++++ 8 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 apps/opencs/model/tools/mergeoperation.cpp create mode 100644 apps/opencs/model/tools/mergeoperation.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 9cc9a2971..eb378f5dd 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -42,6 +42,7 @@ opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck + mergeoperation ) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index b4d873245..87e3679c5 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2385,6 +2385,12 @@ void CSMDoc::Document::runSearch (const CSMWorld::UniversalId& searchId, const C emit stateChanged (getState(), this); } +void CSMDoc::Document::runMerge (const boost::filesystem::path& target) +{ + mTools.runMerge (target); + emit stateChanged (getState(), this); +} + void CSMDoc::Document::abortOperation (int type) { if (type==State_Saving) diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 557f3cb23..706fc3a81 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -125,7 +125,9 @@ namespace CSMDoc CSMWorld::UniversalId newSearch(); void runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search); - + + void runMerge (const boost::filesystem::path& target); + void abortOperation (int type); const CSMWorld::Data& getData() const; @@ -171,4 +173,3 @@ namespace CSMDoc } #endif - diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp new file mode 100644 index 000000000..65cfdb4ff --- /dev/null +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -0,0 +1,15 @@ + +#include "mergeoperation.hpp" + +#include "../doc/state.hpp" + +CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document) +: CSMDoc::Operation (CSMDoc::State_Merging, true) +{ + +} + +void CSMTools::MergeOperation::setTarget (const boost::filesystem::path& target) +{ + +} diff --git a/apps/opencs/model/tools/mergeoperation.hpp b/apps/opencs/model/tools/mergeoperation.hpp new file mode 100644 index 000000000..84526b2f2 --- /dev/null +++ b/apps/opencs/model/tools/mergeoperation.hpp @@ -0,0 +1,28 @@ +#ifndef CSM_TOOLS_MERGEOPERATION_H +#define CSM_TOOLS_MERGEOPERATION_H + +#include + +#include "../doc/operation.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMTools +{ + class MergeOperation : public CSMDoc::Operation + { + + + public: + + MergeOperation (CSMDoc::Document& document); + + /// \attention Do not call this function while a merge is running. + void setTarget (const boost::filesystem::path& target); + }; +} + +#endif diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 0c6e36a61..e9165451d 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -28,6 +28,7 @@ #include "pathgridcheck.hpp" #include "soundgencheck.hpp" #include "magiceffectcheck.hpp" +#include "mergeoperation.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -35,6 +36,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { case CSMDoc::State_Verifying: return &mVerifier; case CSMDoc::State_Searching: return &mSearch; + case CSMDoc::State_Merging: return &mMerge; } return 0; @@ -53,7 +55,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() std::vector settings; settings.push_back ("script-editor/warnings"); - + mVerifierOperation->configureSettings (settings); connect (&mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); @@ -122,7 +124,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() CSMTools::Tools::Tools (CSMDoc::Document& document) : mDocument (document), mData (document.getData()), mVerifierOperation (0), - mSearchOperation (0), mNextReportNumber (0) + mSearchOperation (0), mMergeOperation (0), mNextReportNumber (0) { // index 0: load error log mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); @@ -148,6 +150,12 @@ CSMTools::Tools::~Tools() delete mSearchOperation; } + if (mMergeOperation) + { + mMerge.abortAndWait(); + delete mMergeOperation; + } + for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) delete iter->second; } @@ -159,7 +167,7 @@ CSMWorld::UniversalId CSMTools::Tools::runVerifier (const CSMWorld::UniversalId& if (mReports.find (reportNumber)==mReports.end()) mReports.insert (std::make_pair (reportNumber, new ReportModel)); - + mActiveReports[CSMDoc::State_Verifying] = reportNumber; getVerifier()->start(); @@ -189,6 +197,21 @@ void CSMTools::Tools::runSearch (const CSMWorld::UniversalId& searchId, const Se mSearch.start(); } +void CSMTools::Tools::runMerge (const boost::filesystem::path& target) +{ + // not setting an active report, because merge does not produce messages + + if (!mMergeOperation) + { + mMergeOperation = new MergeOperation (mDocument); + mMerge.setOperation (mMergeOperation); + } + + mMergeOperation->setTarget (target); + + mMerge.start(); +} + void CSMTools::Tools::abortOperation (int type) { if (CSMDoc::OperationHolder *operation = get (type)) @@ -201,6 +224,7 @@ int CSMTools::Tools::getRunningOperations() const { CSMDoc::State_Verifying, CSMDoc::State_Searching, + CSMDoc::State_Merging, -1 }; @@ -231,4 +255,3 @@ void CSMTools::Tools::verifierMessage (const CSMDoc::Message& message, int type) if (iter!=mActiveReports.end()) mReports[iter->second]->add (message); } - diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 78484d15d..952099928 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -1,9 +1,11 @@ #ifndef CSM_TOOLS_TOOLS_H #define CSM_TOOLS_TOOLS_H +#include + #include -#include +#include #include "../doc/operationholder.hpp" @@ -24,6 +26,7 @@ namespace CSMTools class ReportModel; class Search; class SearchOperation; + class MergeOperation; class Tools : public QObject { @@ -35,6 +38,8 @@ namespace CSMTools CSMDoc::OperationHolder mVerifier; SearchOperation *mSearchOperation; CSMDoc::OperationHolder mSearch; + MergeOperation *mMergeOperation; + CSMDoc::OperationHolder mMerge; std::map mReports; int mNextReportNumber; std::map mActiveReports; // type, report number @@ -67,7 +72,9 @@ namespace CSMTools CSMWorld::UniversalId newSearch(); void runSearch (const CSMWorld::UniversalId& searchId, const Search& search); - + + void runMerge (const boost::filesystem::path& target); + void abortOperation (int type); ///< \attention The operation is not aborted immediately. diff --git a/apps/opencs/view/tools/merge.cpp b/apps/opencs/view/tools/merge.cpp index baadd9179..faf7be3b4 100644 --- a/apps/opencs/view/tools/merge.cpp +++ b/apps/opencs/view/tools/merge.cpp @@ -66,7 +66,9 @@ CSVTools::Merge::Merge (QWidget *parent) QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this); connect (buttons->button (QDialogButtonBox::Cancel), SIGNAL (clicked()), this, SLOT (reject())); + mOkay = new QPushButton ("Merge", this); + connect (mOkay, SIGNAL (clicked()), this, SLOT (accept())); mOkay->setDefault (true); buttons->addButton (mOkay, QDialogButtonBox::AcceptRole); @@ -109,6 +111,12 @@ void CSVTools::Merge::cancel() void CSVTools::Merge::accept() { QDialog::accept(); + + if ((mDocument->getState() & CSMDoc::State_Merging)==0) + { + mDocument->runMerge (mAdjuster->getPath()); + hide(); + } } void CSVTools::Merge::reject() From 9423603e9431eab36ed6f53a4bdca680879848ab Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 13 Aug 2015 12:53:00 +0200 Subject: [PATCH 180/441] inheriting Merge from QWidget instead of QDialog, because QDialog is bloody useless for non-modal dialogues (which makes the class completely useless, since modal dialogues are the spawn of Satan) (cherry picked from commit e2377396a7cf7b9cd1ec91e0f385721a124a49cc) --- apps/opencs/editor.cpp | 2 ++ apps/opencs/view/tools/merge.cpp | 24 ++++++++++++++---------- apps/opencs/view/tools/merge.hpp | 15 +++++++-------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 1de1e1865..e680f5a85 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -550,4 +550,6 @@ void CS::Editor::mergeDocument (CSMDoc::Document *document) { mMerge.configure (document); mMerge.show(); + mMerge.raise(); + mMerge.activateWindow(); } diff --git a/apps/opencs/view/tools/merge.cpp b/apps/opencs/view/tools/merge.cpp index faf7be3b4..f775390ee 100644 --- a/apps/opencs/view/tools/merge.cpp +++ b/apps/opencs/view/tools/merge.cpp @@ -7,14 +7,26 @@ #include #include #include +#include #include "../../model/doc/document.hpp" #include "../doc/filewidget.hpp" #include "../doc/adjusterwidget.hpp" +void CSVTools::Merge::keyPressEvent (QKeyEvent *event) +{ + if (event->key()==Qt::Key_Escape) + { + event->accept(); + cancel(); + } + else + QWidget::keyPressEvent (event); +} + CSVTools::Merge::Merge (QWidget *parent) -: QDialog (parent), mDocument (0) +: QWidget (parent), mDocument (0) { setWindowTitle ("Merge Content Files into a new Game File"); @@ -65,7 +77,7 @@ CSVTools::Merge::Merge (QWidget *parent) // buttons QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this); - connect (buttons->button (QDialogButtonBox::Cancel), SIGNAL (clicked()), this, SLOT (reject())); + connect (buttons->button (QDialogButtonBox::Cancel), SIGNAL (clicked()), this, SLOT (cancel())); mOkay = new QPushButton ("Merge", this); connect (mOkay, SIGNAL (clicked()), this, SLOT (accept())); @@ -110,8 +122,6 @@ void CSVTools::Merge::cancel() void CSVTools::Merge::accept() { - QDialog::accept(); - if ((mDocument->getState() & CSMDoc::State_Merging)==0) { mDocument->runMerge (mAdjuster->getPath()); @@ -119,12 +129,6 @@ void CSVTools::Merge::accept() } } -void CSVTools::Merge::reject() -{ - QDialog::reject(); - cancel(); -} - void CSVTools::Merge::stateChanged (bool valid) { mOkay->setEnabled (valid); diff --git a/apps/opencs/view/tools/merge.hpp b/apps/opencs/view/tools/merge.hpp index 4538baebe..b8369ffa3 100644 --- a/apps/opencs/view/tools/merge.hpp +++ b/apps/opencs/view/tools/merge.hpp @@ -1,7 +1,7 @@ #ifndef CSV_TOOLS_REPORTTABLE_H #define CSV_TOOLS_REPORTTABLE_H -#include +#include #include @@ -21,7 +21,7 @@ namespace CSVDoc namespace CSVTools { - class Merge : public QDialog + class Merge : public QWidget { Q_OBJECT @@ -31,6 +31,8 @@ namespace CSVTools CSVDoc::FileWidget *mNewFile; CSVDoc::AdjusterWidget *mAdjuster; + void keyPressEvent (QKeyEvent *event); + public: Merge (QWidget *parent = 0); @@ -42,18 +44,15 @@ namespace CSVTools CSMDoc::Document *getDocument() const; - void cancel(); - public slots: - virtual void accept(); - - virtual void reject(); + void cancel(); private slots: - void stateChanged (bool valid); + void accept(); + void stateChanged (bool valid); }; } From 0b8e04d6a8911c7b3a149c194ccf80ca8918b6ce Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 13 Aug 2015 14:49:32 +0200 Subject: [PATCH 181/441] forgot to connect merge operation signals (cherry picked from commit d8655f2ff862dbaf90fb285ef01cdf6891cdbea6) --- apps/opencs/model/tools/tools.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index e9165451d..67c917b7f 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -134,6 +134,10 @@ CSMTools::Tools::Tools (CSMDoc::Document& document) connect (&mSearch, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); connect (&mSearch, SIGNAL (reportMessage (const CSMDoc::Message&, int)), this, SLOT (verifierMessage (const CSMDoc::Message&, int))); + + connect (&mMerge, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); + connect (&mMerge, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); + // don't need to connect report message, since there are no messages for merge } CSMTools::Tools::~Tools() From 3eef192e4d4a052d17af686ba469ff7ed07b4a22 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 16 Aug 2015 15:24:48 +0200 Subject: [PATCH 182/441] create merged document and open a view for it (document is still empty at this point) (cherry picked from commit 1b663f01aff94811a46825467cad8f38ed025b24) Conflicts: apps/opencs/CMakeLists.txt apps/opencs/model/doc/documentmanager.cpp --- apps/opencs/CMakeLists.txt | 8 +++++-- apps/opencs/editor.cpp | 3 ++- apps/opencs/model/doc/document.cpp | 4 +++- apps/opencs/model/doc/document.hpp | 6 ++++- apps/opencs/model/doc/documentmanager.cpp | 16 ++++++++++++- apps/opencs/model/doc/documentmanager.hpp | 13 +++++++++++ apps/opencs/model/doc/operation.hpp | 4 +++- apps/opencs/model/tools/mergeoperation.cpp | 15 +++++++++++-- apps/opencs/model/tools/mergeoperation.hpp | 19 ++++++++++++++-- apps/opencs/model/tools/mergestages.cpp | 18 +++++++++++++++ apps/opencs/model/tools/mergestages.hpp | 26 ++++++++++++++++++++++ apps/opencs/model/tools/mergestate.hpp | 20 +++++++++++++++++ apps/opencs/model/tools/tools.cpp | 4 +++- apps/opencs/model/tools/tools.hpp | 7 +++++- apps/opencs/view/tools/merge.cpp | 13 ++++++++--- apps/opencs/view/tools/merge.hpp | 4 +++- 16 files changed, 163 insertions(+), 17 deletions(-) create mode 100644 apps/opencs/model/tools/mergestages.cpp create mode 100644 apps/opencs/model/tools/mergestages.hpp create mode 100644 apps/opencs/model/tools/mergestate.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index eb378f5dd..3b756495d 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -35,14 +35,18 @@ opencs_hdrs_noqt (model/world opencs_units (model/tools - tools reportmodel + tools reportmodel mergeoperation ) opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck - mergeoperation + mergestages + ) + +opencs_hdrs_noqt (model/tools + mergestate ) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index e680f5a85..3721936ce 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -26,7 +26,8 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) : mUserSettings (mCfgMgr), mOverlaySystem (0), mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), mPid(""), - mLock(), mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) + mLock(), mMerge (mDocumentManager), + mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) { std::pair > config = readConfig(); diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 87e3679c5..9cfdb5795 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2296,6 +2296,8 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); + connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SIGNAL (mergeDone (CSMDoc::Document*))); connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); @@ -2385,7 +2387,7 @@ void CSMDoc::Document::runSearch (const CSMWorld::UniversalId& searchId, const C emit stateChanged (getState(), this); } -void CSMDoc::Document::runMerge (const boost::filesystem::path& target) +void CSMDoc::Document::runMerge (std::auto_ptr target) { mTools.runMerge (target); emit stateChanged (getState(), this); diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 706fc3a81..e9059f629 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -126,7 +126,7 @@ namespace CSMDoc void runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search); - void runMerge (const boost::filesystem::path& target); + void runMerge (std::auto_ptr target); void abortOperation (int type); @@ -156,6 +156,10 @@ namespace CSMDoc void progress (int current, int max, int type, int threads, CSMDoc::Document *document); + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); + private slots: void modificationStateChanged (bool clean); diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index e8668d3ef..af10dc9d6 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -56,10 +56,24 @@ bool CSMDoc::DocumentManager::isEmpty() void CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { - Document *document = new Document (mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); + Document *document = makeDocument (files, savePath, new_); + insertDocument (document); +} + +CSMDoc::Document *CSMDoc::DocumentManager::makeDocument ( + const std::vector< boost::filesystem::path >& files, + const boost::filesystem::path& savePath, bool new_) +{ + return new Document (mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); +} +void CSMDoc::DocumentManager::insertDocument (CSMDoc::Document *document) +{ mDocuments.push_back (document); + connect (document, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SLOT (insertDocument (CSMDoc::Document*))); + emit loadRequest (document); mLoader.hasThingsToDo().wakeAll(); diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index 5a892cc45..1e8492828 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -50,6 +50,15 @@ namespace CSMDoc ///< \param new_ Do not load the last content file in \a files and instead create in an /// appropriate way. + /// Create a new document. The ownership of the created document is transferred to + /// the calling function. The DocumentManager does not manage it. Loading has not + /// taken place at the point when the document is returned. + /// + /// \param new_ Do not load the last content file in \a files and instead create in an + /// appropriate way. + Document *makeDocument (const std::vector< boost::filesystem::path >& files, + const boost::filesystem::path& savePath, bool new_); + void setResourceDir (const boost::filesystem::path& parResDir); void setEncoding (ToUTF8::FromType encoding); @@ -79,6 +88,10 @@ namespace CSMDoc void removeDocument (CSMDoc::Document *document); ///< Emits the lastDocumentDeleted signal, if applicable. + /// Hand over document to *this. The ownership is transferred. The DocumentManager + /// will initiate the load procedure, if necessary + void insertDocument (CSMDoc::Document *document); + signals: void documentAdded (CSMDoc::Document *document); diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index 703f9d44b..3c86a6e23 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -83,7 +83,9 @@ namespace CSMDoc void executeStage(); - void operationDone(); + protected slots: + + virtual void operationDone(); }; } diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp index 65cfdb4ff..c93452dad 100644 --- a/apps/opencs/model/tools/mergeoperation.cpp +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -2,14 +2,25 @@ #include "mergeoperation.hpp" #include "../doc/state.hpp" +#include "../doc/document.hpp" + +#include "mergestages.hpp" CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document) -: CSMDoc::Operation (CSMDoc::State_Merging, true) +: CSMDoc::Operation (CSMDoc::State_Merging, true), mState (document) { + appendStage (new FinishMergedDocumentStage (mState)); +} +void CSMTools::MergeOperation::setTarget (std::auto_ptr document) +{ + mState.mTarget = document; } -void CSMTools::MergeOperation::setTarget (const boost::filesystem::path& target) +void CSMTools::MergeOperation::operationDone() { + CSMDoc::Operation::operationDone(); + if (mState.mCompleted) + emit mergeDone (mState.mTarget.release()); } diff --git a/apps/opencs/model/tools/mergeoperation.hpp b/apps/opencs/model/tools/mergeoperation.hpp index 84526b2f2..dc958d31b 100644 --- a/apps/opencs/model/tools/mergeoperation.hpp +++ b/apps/opencs/model/tools/mergeoperation.hpp @@ -1,10 +1,12 @@ #ifndef CSM_TOOLS_MERGEOPERATION_H #define CSM_TOOLS_MERGEOPERATION_H -#include +#include #include "../doc/operation.hpp" +#include "mergestate.hpp" + namespace CSMDoc { class Document; @@ -14,14 +16,27 @@ namespace CSMTools { class MergeOperation : public CSMDoc::Operation { + Q_OBJECT + MergeState mState; public: MergeOperation (CSMDoc::Document& document); /// \attention Do not call this function while a merge is running. - void setTarget (const boost::filesystem::path& target); + void setTarget (std::auto_ptr document); + + protected slots: + + virtual void operationDone(); + + signals: + + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); + }; } diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp new file mode 100644 index 000000000..16ea0495a --- /dev/null +++ b/apps/opencs/model/tools/mergestages.cpp @@ -0,0 +1,18 @@ + +#include "mergestages.hpp" + +#include "mergestate.hpp" + +CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage (MergeState& state) +: mState (state) +{} + +int CSMTools::FinishMergedDocumentStage::setup() +{ + return 1; +} + +void CSMTools::FinishMergedDocumentStage::perform (int stage, CSMDoc::Messages& messages) +{ + mState.mCompleted = true; +} diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp new file mode 100644 index 000000000..138b89d72 --- /dev/null +++ b/apps/opencs/model/tools/mergestages.hpp @@ -0,0 +1,26 @@ +#ifndef CSM_TOOLS_MERGESTAGES_H +#define CSM_TOOLS_MERGESTAGES_H + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + struct MergeState; + + class FinishMergedDocumentStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + FinishMergedDocumentStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/mergestate.hpp b/apps/opencs/model/tools/mergestate.hpp new file mode 100644 index 000000000..4482ba6f4 --- /dev/null +++ b/apps/opencs/model/tools/mergestate.hpp @@ -0,0 +1,20 @@ +#ifndef CSM_TOOLS_MERGESTATE_H +#define CSM_TOOLS_MERGESTATE_H + +#include + +#include "../doc/document.hpp" + +namespace CSMTools +{ + struct MergeState + { + std::auto_ptr mTarget; + CSMDoc::Document& mSource; + bool mCompleted; + + MergeState (CSMDoc::Document& source) : mSource (source), mCompleted (false) {} + }; +} + +#endif diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 67c917b7f..43a31cdcc 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -201,7 +201,7 @@ void CSMTools::Tools::runSearch (const CSMWorld::UniversalId& searchId, const Se mSearch.start(); } -void CSMTools::Tools::runMerge (const boost::filesystem::path& target) +void CSMTools::Tools::runMerge (std::auto_ptr target) { // not setting an active report, because merge does not produce messages @@ -209,6 +209,8 @@ void CSMTools::Tools::runMerge (const boost::filesystem::path& target) { mMergeOperation = new MergeOperation (mDocument); mMerge.setOperation (mMergeOperation); + connect (mMergeOperation, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SIGNAL (mergeDone (CSMDoc::Document*))); } mMergeOperation->setTarget (target); diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 952099928..2302fe2e0 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -1,6 +1,7 @@ #ifndef CSM_TOOLS_TOOLS_H #define CSM_TOOLS_TOOLS_H +#include #include #include @@ -73,7 +74,7 @@ namespace CSMTools void runSearch (const CSMWorld::UniversalId& searchId, const Search& search); - void runMerge (const boost::filesystem::path& target); + void runMerge (std::auto_ptr target); void abortOperation (int type); ///< \attention The operation is not aborted immediately. @@ -92,6 +93,10 @@ namespace CSMTools void progress (int current, int max, int type); void done (int type, bool failed); + + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); }; } diff --git a/apps/opencs/view/tools/merge.cpp b/apps/opencs/view/tools/merge.cpp index f775390ee..566a5ee06 100644 --- a/apps/opencs/view/tools/merge.cpp +++ b/apps/opencs/view/tools/merge.cpp @@ -10,6 +10,7 @@ #include #include "../../model/doc/document.hpp" +#include "../../model/doc/documentmanager.hpp" #include "../doc/filewidget.hpp" #include "../doc/adjusterwidget.hpp" @@ -25,8 +26,8 @@ void CSVTools::Merge::keyPressEvent (QKeyEvent *event) QWidget::keyPressEvent (event); } -CSVTools::Merge::Merge (QWidget *parent) -: QWidget (parent), mDocument (0) +CSVTools::Merge::Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent) +: QWidget (parent), mDocument (0), mDocumentManager (documentManager) { setWindowTitle ("Merge Content Files into a new Game File"); @@ -124,7 +125,13 @@ void CSVTools::Merge::accept() { if ((mDocument->getState() & CSMDoc::State_Merging)==0) { - mDocument->runMerge (mAdjuster->getPath()); + std::vector< boost::filesystem::path > files (1, mAdjuster->getPath()); + + std::auto_ptr target ( + mDocumentManager.makeDocument (files, files[0], true)); + + mDocument->runMerge (target); + hide(); } } diff --git a/apps/opencs/view/tools/merge.hpp b/apps/opencs/view/tools/merge.hpp index b8369ffa3..d4ed7e34b 100644 --- a/apps/opencs/view/tools/merge.hpp +++ b/apps/opencs/view/tools/merge.hpp @@ -11,6 +11,7 @@ class QListWidget; namespace CSMDoc { class Document; + class DocumentManager; } namespace CSVDoc @@ -30,12 +31,13 @@ namespace CSVTools QListWidget *mFiles; CSVDoc::FileWidget *mNewFile; CSVDoc::AdjusterWidget *mAdjuster; + CSMDoc::DocumentManager& mDocumentManager; void keyPressEvent (QKeyEvent *event); public: - Merge (QWidget *parent = 0); + Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent = 0); /// Configure dialogue for a new merge void configure (CSMDoc::Document *document); From 55dfe48ef71d866e5da528b1b34c384adb3dcaee Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 16 Aug 2015 18:27:17 +0200 Subject: [PATCH 183/441] copy meta data from game file when merging (cherry picked from commit 47dd9505a9464dc5c947f409c3c4a80cf4d8e395) Conflicts: apps/opencs/model/doc/document.cpp apps/opencs/model/world/data.cpp --- apps/opencs/model/doc/document.cpp | 2 +- apps/opencs/model/tools/mergeoperation.cpp | 4 ++-- apps/opencs/model/tools/mergeoperation.hpp | 4 +++- apps/opencs/model/tools/mergestages.cpp | 27 ++++++++++++++++++++-- apps/opencs/model/tools/mergestages.hpp | 5 +++- apps/opencs/model/tools/tools.cpp | 6 ++--- apps/opencs/model/tools/tools.hpp | 5 +++- apps/opencs/model/world/data.cpp | 6 +++++ apps/opencs/model/world/data.hpp | 2 ++ 9 files changed, 50 insertions(+), 11 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 9cfdb5795..184a0e38a 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2251,7 +2251,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, const std::vector& blacklistedScripts) : mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager), - mTools (*this), + mTools (*this, encoding), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), mSavingOperation (*this, mProjectPath, encoding), diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp index c93452dad..9e5fb2745 100644 --- a/apps/opencs/model/tools/mergeoperation.cpp +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -6,10 +6,10 @@ #include "mergestages.hpp" -CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document) +CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding) : CSMDoc::Operation (CSMDoc::State_Merging, true), mState (document) { - appendStage (new FinishMergedDocumentStage (mState)); + appendStage (new FinishMergedDocumentStage (mState, encoding)); } void CSMTools::MergeOperation::setTarget (std::auto_ptr document) diff --git a/apps/opencs/model/tools/mergeoperation.hpp b/apps/opencs/model/tools/mergeoperation.hpp index dc958d31b..bdaeb2ccd 100644 --- a/apps/opencs/model/tools/mergeoperation.hpp +++ b/apps/opencs/model/tools/mergeoperation.hpp @@ -3,6 +3,8 @@ #include +#include + #include "../doc/operation.hpp" #include "mergestate.hpp" @@ -22,7 +24,7 @@ namespace CSMTools public: - MergeOperation (CSMDoc::Document& document); + MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding); /// \attention Do not call this function while a merge is running. void setTarget (std::auto_ptr document); diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 16ea0495a..776808b26 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -3,8 +3,11 @@ #include "mergestate.hpp" -CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage (MergeState& state) -: mState (state) +#include "../doc/document.hpp" +#include "../world/data.hpp" + +CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding) +: mState (state), mEncoder (encoding) {} int CSMTools::FinishMergedDocumentStage::setup() @@ -14,5 +17,25 @@ int CSMTools::FinishMergedDocumentStage::setup() void CSMTools::FinishMergedDocumentStage::perform (int stage, CSMDoc::Messages& messages) { + // We know that the content file list contains at least two entries and that the first one + // does exist on disc (otherwise it would have been impossible to initiate a merge on that + // document). + boost::filesystem::path path = mState.mSource.getContentFiles()[0]; + + ESM::ESMReader reader; + reader.setEncoder (&mEncoder); + reader.open (path.string()); + + CSMWorld::MetaData source; + source.mId = "sys::meta"; + source.load (reader); + + CSMWorld::MetaData target = mState.mTarget->getData().getMetaData(); + + target.mAuthor = source.mAuthor; + target.mDescription = source.mDescription; + + mState.mTarget->getData().setMetaData (target); + mState.mCompleted = true; } diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index 138b89d72..9844feaca 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -1,6 +1,8 @@ #ifndef CSM_TOOLS_MERGESTAGES_H #define CSM_TOOLS_MERGESTAGES_H +#include + #include "../doc/stage.hpp" namespace CSMTools @@ -10,10 +12,11 @@ namespace CSMTools class FinishMergedDocumentStage : public CSMDoc::Stage { MergeState& mState; + ToUTF8::Utf8Encoder mEncoder; public: - FinishMergedDocumentStage (MergeState& state); + FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding); virtual int setup(); ///< \return number of steps diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 43a31cdcc..8759f9c31 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -122,9 +122,9 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() return &mVerifier; } -CSMTools::Tools::Tools (CSMDoc::Document& document) +CSMTools::Tools::Tools (CSMDoc::Document& document, ToUTF8::FromType encoding) : mDocument (document), mData (document.getData()), mVerifierOperation (0), - mSearchOperation (0), mMergeOperation (0), mNextReportNumber (0) + mSearchOperation (0), mMergeOperation (0), mNextReportNumber (0), mEncoding (encoding) { // index 0: load error log mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); @@ -207,7 +207,7 @@ void CSMTools::Tools::runMerge (std::auto_ptr target) if (!mMergeOperation) { - mMergeOperation = new MergeOperation (mDocument); + mMergeOperation = new MergeOperation (mDocument, mEncoding); mMerge.setOperation (mMergeOperation); connect (mMergeOperation, SIGNAL (mergeDone (CSMDoc::Document*)), this, SIGNAL (mergeDone (CSMDoc::Document*))); diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 2302fe2e0..e16a3854c 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include @@ -44,6 +46,7 @@ namespace CSMTools std::map mReports; int mNextReportNumber; std::map mActiveReports; // type, report number + ToUTF8::FromType mEncoding; // not implemented Tools (const Tools&); @@ -59,7 +62,7 @@ namespace CSMTools public: - Tools (CSMDoc::Document& document); + Tools (CSMDoc::Document& document, ToUTF8::FromType encoding); virtual ~Tools(); diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index b6b7f689e..b5722ab57 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -928,6 +928,12 @@ const CSMWorld::MetaData& CSMWorld::Data::getMetaData() const return mMetaData.getRecord (0).get(); } +void CSMWorld::Data::setMetaData (const MetaData& metaData) +{ + Record record (RecordBase::State_ModifiedOnly, 0, &metaData); + mMetaData.setRecord (0, record); +} + QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id) { std::map::iterator iter = mModelIndex.find (id.getType()); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 1aa9db0b1..c9fe956b1 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -247,6 +247,8 @@ namespace CSMWorld const MetaData& getMetaData() const; + void setMetaData (const MetaData& metaData); + QAbstractItemModel *getTableModel (const UniversalId& id); ///< If no table model is available for \a id, an exception is thrown. /// From 973c65bc822945b69e405f55f8608b29c4a7d256 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 21 Aug 2015 14:02:32 +0200 Subject: [PATCH 184/441] merge id collections (cherry picked from commit 3902513e65206908c0f0f54db213a7c9ea7f2fd0) --- apps/opencs/model/tools/mergeoperation.cpp | 23 ++++++++++ apps/opencs/model/tools/mergestages.hpp | 53 +++++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp index 9e5fb2745..4a85eca79 100644 --- a/apps/opencs/model/tools/mergeoperation.cpp +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -10,6 +10,29 @@ CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::Fr : CSMDoc::Operation (CSMDoc::State_Merging, true), mState (document) { appendStage (new FinishMergedDocumentStage (mState, encoding)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGlobals)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGmsts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSkills)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getClasses)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFactions)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRaces)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSounds)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getScripts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRegions)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBirthsigns)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSpells)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopics)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournals)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getCells)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFilters)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getEnchantments)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBodyParts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getDebugProfiles)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSoundGens)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getMagicEffects)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getStartScripts)); + + /// \todo TopicInfo, JournalInfo, Referencables, References, Land, LandTextures, Pathgrids } void CSMTools::MergeOperation::setTarget (std::auto_ptr document) diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index 9844feaca..749fab18c 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -5,10 +5,12 @@ #include "../doc/stage.hpp" +#include "../world/data.hpp" + +#include "mergestate.hpp" + namespace CSMTools { - struct MergeState; - class FinishMergedDocumentStage : public CSMDoc::Stage { MergeState& mState; @@ -24,6 +26,53 @@ namespace CSMTools virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; + + template + class MergeIdCollectionStage : public CSMDoc::Stage + { + typedef typename CSMWorld::IdCollection Collection; + + MergeState& mState; + Collection& (CSMWorld::Data::*mAccessor)(); + + public: + + MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + template + MergeIdCollectionStage::MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()) + : mState (state), mAccessor (accessor) + {} + + template + int MergeIdCollectionStage::setup() + { + return 1; + } + + template + void MergeIdCollectionStage::perform (int stage, CSMDoc::Messages& messages) + { + const Collection& source = (mState.mSource.getData().*mAccessor)(); + Collection& target = (mState.mTarget->getData().*mAccessor)(); + + int size = source.getSize(); + + for (int i=0; i& record = source.getRecord (i); + + if (!record.isDeleted()) + target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_BaseOnly, &record.get())); + } + } } #endif From 024a5405e0995c4695604491f93a907641db3e8f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 23 Aug 2015 12:37:45 +0200 Subject: [PATCH 185/441] flag newly merged documents as dirty (triggering an 'are you sure' dialogue when closing without saving first) (cherry picked from commit c92898a5bc18584708bd9c0e1e76c517adb3499b) Conflicts: apps/opencs/model/doc/document.cpp apps/opencs/model/doc/document.hpp --- apps/opencs/model/doc/document.cpp | 12 ++++++++++-- apps/opencs/model/doc/document.hpp | 4 ++++ apps/opencs/model/tools/tools.cpp | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 184a0e38a..55034cb4e 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2258,7 +2258,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, mSaving (&mSavingOperation), mResDir(resDir), mRunner (mProjectPath), mPhysics(boost::shared_ptr()), - mIdCompletionManager(mData) + mIdCompletionManager(mData), mDirty (false) { if (mContentFiles.empty()) throw std::runtime_error ("Empty content file sequence"); @@ -2322,7 +2322,7 @@ int CSMDoc::Document::getState() const { int state = 0; - if (!mUndoStack.isClean()) + if (!mUndoStack.isClean() || mDirty) state |= State_Modified; if (mSaving.isRunning()) @@ -2414,6 +2414,9 @@ void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type) void CSMDoc::Document::operationDone (int type, bool failed) { + if (type==CSMDoc::State_Saving && !failed) + mDirty = false; + emit stateChanged (getState(), this); } @@ -2498,3 +2501,8 @@ CSMWorld::IdCompletionManager &CSMDoc::Document::getIdCompletionManager() { return mIdCompletionManager; } + +void CSMDoc::Document::flagAsDirty() +{ + mDirty = true; +} diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index e9059f629..e737bb1c4 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -67,6 +67,8 @@ namespace CSMDoc Blacklist mBlacklist; Runner mRunner; boost::shared_ptr mPhysics; + bool mDirty; + CSMWorld::IdCompletionManager mIdCompletionManager; // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is @@ -150,6 +152,8 @@ namespace CSMDoc CSMWorld::IdCompletionManager &getIdCompletionManager(); + void flagAsDirty(); + signals: void stateChanged (int state, CSMDoc::Document *document); diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 8759f9c31..fdbf406f1 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -213,6 +213,8 @@ void CSMTools::Tools::runMerge (std::auto_ptr target) this, SIGNAL (mergeDone (CSMDoc::Document*))); } + target->flagAsDirty(); + mMergeOperation->setTarget (target); mMerge.start(); From 98cac624a93fc039f055a788fe19ba8e2e46656e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 23 Aug 2015 12:58:49 +0200 Subject: [PATCH 186/441] added info tables and pathgrid table to merge operation (cherry picked from commit 103073150e87d62582649ea6943fdc510eb5ca59) --- apps/opencs/model/tools/mergeoperation.cpp | 5 ++++- apps/opencs/model/tools/mergestages.hpp | 16 +++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp index 4a85eca79..d010e343b 100644 --- a/apps/opencs/model/tools/mergeoperation.cpp +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -31,8 +31,11 @@ CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::Fr appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSoundGens)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getMagicEffects)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getStartScripts)); + appendStage (new MergeIdCollectionStage > (mState, &CSMWorld::Data::getPathgrids)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopicInfos)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournalInfos)); - /// \todo TopicInfo, JournalInfo, Referencables, References, Land, LandTextures, Pathgrids + /// \todo Referencables, References, Land, LandTextures } void CSMTools::MergeOperation::setTarget (std::auto_ptr document) diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index 749fab18c..f9d29420b 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -27,11 +27,9 @@ namespace CSMTools ///< Messages resulting from this stage will be appended to \a messages. }; - template + template > class MergeIdCollectionStage : public CSMDoc::Stage { - typedef typename CSMWorld::IdCollection Collection; - MergeState& mState; Collection& (CSMWorld::Data::*mAccessor)(); @@ -46,19 +44,19 @@ namespace CSMTools ///< Messages resulting from this stage will be appended to \a messages. }; - template - MergeIdCollectionStage::MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()) + template + MergeIdCollectionStage::MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()) : mState (state), mAccessor (accessor) {} - template - int MergeIdCollectionStage::setup() + template + int MergeIdCollectionStage::setup() { return 1; } - template - void MergeIdCollectionStage::perform (int stage, CSMDoc::Messages& messages) + template + void MergeIdCollectionStage::perform (int stage, CSMDoc::Messages& messages) { const Collection& source = (mState.mSource.getData().*mAccessor)(); Collection& target = (mState.mTarget->getData().*mAccessor)(); From e33314ad813350f982b4f713cb9bed7d741e927e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 23 Aug 2015 13:04:42 +0200 Subject: [PATCH 187/441] made merge operation more fluent (cherry picked from commit 16dda281cee3fb1bab7276cc21448b754d5db3c5) --- apps/opencs/model/tools/mergestages.hpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index f9d29420b..af7f06d60 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -1,6 +1,8 @@ #ifndef CSM_TOOLS_MERGESTAGES_H #define CSM_TOOLS_MERGESTAGES_H +#include + #include #include "../doc/stage.hpp" @@ -33,6 +35,8 @@ namespace CSMTools MergeState& mState; Collection& (CSMWorld::Data::*mAccessor)(); + static const int stepSize = 1000; + public: MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()); @@ -52,7 +56,16 @@ namespace CSMTools template int MergeIdCollectionStage::setup() { - return 1; + const Collection& source = (mState.mSource.getData().*mAccessor)(); + + int size = source.getSize(); + + int steps = size / stepSize; + + if (size % stepSize) + ++steps; + + return steps; } template @@ -61,9 +74,10 @@ namespace CSMTools const Collection& source = (mState.mSource.getData().*mAccessor)(); Collection& target = (mState.mTarget->getData().*mAccessor)(); - int size = source.getSize(); + int begin = stage * stepSize; + int end = std::min ((stage+1) * stepSize, source.getSize()); - for (int i=0; i& record = source.getRecord (i); From 72aa99826161f34d0f49775c4cff316757f8ef89 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Aug 2015 08:43:03 +0200 Subject: [PATCH 188/441] simplifying merge stage (cherry picked from commit 40753aa9a3759e247b1fc907f5ef61af2656d533) --- apps/opencs/model/tools/mergestages.hpp | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index af7f06d60..c0ed16787 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -35,8 +35,6 @@ namespace CSMTools MergeState& mState; Collection& (CSMWorld::Data::*mAccessor)(); - static const int stepSize = 1000; - public: MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()); @@ -56,16 +54,7 @@ namespace CSMTools template int MergeIdCollectionStage::setup() { - const Collection& source = (mState.mSource.getData().*mAccessor)(); - - int size = source.getSize(); - - int steps = size / stepSize; - - if (size % stepSize) - ++steps; - - return steps; + return (mState.mSource.getData().*mAccessor)().getSize(); } template @@ -74,16 +63,10 @@ namespace CSMTools const Collection& source = (mState.mSource.getData().*mAccessor)(); Collection& target = (mState.mTarget->getData().*mAccessor)(); - int begin = stage * stepSize; - int end = std::min ((stage+1) * stepSize, source.getSize()); - - for (int i=begin; i& record = source.getRecord (i); + const CSMWorld::Record& record = source.getRecord (stage); - if (!record.isDeleted()) - target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_BaseOnly, &record.get())); - } + if (!record.isDeleted()) + target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_BaseOnly, &record.get())); } } From 81188a345639903c0ea301fab3a8856a40825106 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Aug 2015 11:39:43 +0200 Subject: [PATCH 189/441] silenced a warning (cherry picked from commit 9cf793c0b519143fd7d3ac3cfb7beb82f65b72f3) Conflicts: apps/opencs/model/doc/document.cpp --- apps/opencs/model/doc/document.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 55034cb4e..925eef659 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2258,7 +2258,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, mSaving (&mSavingOperation), mResDir(resDir), mRunner (mProjectPath), mPhysics(boost::shared_ptr()), - mIdCompletionManager(mData), mDirty (false) + mDirty (false), mIdCompletionManager(mData) { if (mContentFiles.empty()) throw std::runtime_error ("Empty content file sequence"); From 9230c62884d54722ed35962afc162162a7fb54fa Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Aug 2015 11:54:16 +0200 Subject: [PATCH 190/441] merge referenceables table (cherry picked from commit f95950e8d815997da34eb9939b81cd27afb62dbb) --- apps/opencs/model/tools/mergeoperation.cpp | 3 ++- apps/opencs/model/tools/mergestages.cpp | 14 ++++++++++++++ apps/opencs/model/tools/mergestages.hpp | 16 ++++++++++++++++ apps/opencs/model/world/refidcollection.cpp | 5 +++++ apps/opencs/model/world/refidcollection.hpp | 1 + apps/opencs/model/world/refiddata.cpp | 11 +++++++++++ apps/opencs/model/world/refiddata.hpp | 6 ++---- 7 files changed, 51 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp index d010e343b..0f128e86e 100644 --- a/apps/opencs/model/tools/mergeoperation.cpp +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -34,8 +34,9 @@ CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::Fr appendStage (new MergeIdCollectionStage > (mState, &CSMWorld::Data::getPathgrids)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopicInfos)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournalInfos)); + appendStage (new MergeRefIdsStage (mState)); - /// \todo Referencables, References, Land, LandTextures + /// \todo References, Land, LandTextures } void CSMTools::MergeOperation::setTarget (std::auto_ptr document) diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 776808b26..d8bd93824 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -39,3 +39,17 @@ void CSMTools::FinishMergedDocumentStage::perform (int stage, CSMDoc::Messages& mState.mCompleted = true; } + + +CSMTools::MergeRefIdsStage::MergeRefIdsStage (MergeState& state) : mState (state) {} + +int CSMTools::MergeRefIdsStage::setup() +{ + return mState.mSource.getData().getReferenceables().getSize(); +} + +void CSMTools::MergeRefIdsStage::perform (int stage, CSMDoc::Messages& messages) +{ + mState.mSource.getData().getReferenceables().copyTo ( + stage, mState.mTarget->getData().getReferenceables()); +} diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index c0ed16787..308ebe1c3 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -68,6 +68,22 @@ namespace CSMTools if (!record.isDeleted()) target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_BaseOnly, &record.get())); } + + + class MergeRefIdsStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + MergeRefIdsStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; } #endif diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 8ea2bb30a..3644fc264 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -924,3 +924,8 @@ const CSMWorld::NestedRefIdAdapterBase& CSMWorld::RefIdCollection::getNestedAdap } throw std::runtime_error("No such column in the nestedadapters"); } + +void CSMWorld::RefIdCollection::copyTo (int index, RefIdCollection& target) const +{ + mData.copyTo (index, target.mData); +} diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index e8e633663..4db39348e 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -140,6 +140,7 @@ namespace CSMWorld void save (int index, ESM::ESMWriter& writer) const; const RefIdData& getDataSet() const; //I can't figure out a better name for this one :( + void copyTo (int index, RefIdCollection& target) const; }; } diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index c391201e6..a7dc95519 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -357,3 +357,14 @@ void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::U mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), LocalIndex (iter->second->getSize()-1, type))); } + +void CSMWorld::RefIdData::copyTo (int index, RefIdData& target) const +{ + LocalIndex localIndex = globalToLocalIndex (index); + + RefIdDataContainerBase *source = mRecordContainers.find (localIndex.second)->second; + + std::string id = source->getId (localIndex.first); + + target.insertRecord (source->getRecord (localIndex.first), localIndex.second, id); +} diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 85d16a6eb..4507f6028 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -252,11 +252,9 @@ namespace CSMWorld const RefIdDataContainer& getProbes() const; const RefIdDataContainer& getRepairs() const; const RefIdDataContainer& getStatics() const; + + void copyTo (int index, RefIdData& target) const; }; } #endif - - - - From 62daecadf57081140e15e24ec4460499e9059b00 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Aug 2015 12:40:40 +0200 Subject: [PATCH 191/441] fixed record state issues after merge (cherry picked from commit 845cafd61c12142a353119a6304845370037d7a5) --- apps/opencs/model/tools/mergestages.hpp | 2 +- apps/opencs/model/world/refiddata.cpp | 7 +++++-- apps/opencs/model/world/refiddata.hpp | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index 308ebe1c3..5c2243d43 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -66,7 +66,7 @@ namespace CSMTools const CSMWorld::Record& record = source.getRecord (stage); if (!record.isDeleted()) - target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_BaseOnly, &record.get())); + target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_ModifiedOnly, 0, &record.get())); } diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index a7dc95519..7696c3763 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -1,6 +1,7 @@ #include "refiddata.hpp" #include +#include #include @@ -344,7 +345,7 @@ const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStati return mStatics; } -void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) +void CSMWorld::RefIdData::insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) { std::map::iterator iter = mRecordContainers.find (type); @@ -366,5 +367,7 @@ void CSMWorld::RefIdData::copyTo (int index, RefIdData& target) const std::string id = source->getId (localIndex.first); - target.insertRecord (source->getRecord (localIndex.first), localIndex.second, id); + std::auto_ptr newRecord (source->getRecord (localIndex.first).modifiedCopy()); + + target.insertRecord (*newRecord, localIndex.second, id); } diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 4507f6028..8909ae4fb 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -210,7 +210,8 @@ namespace CSMWorld void erase (int index, int count); - void insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id); + void insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, + const std::string& id); const RecordBase& getRecord (const LocalIndex& index) const; From 73ab89bf70f53ef7f5d2e9417d15f703a91794d6 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 26 Aug 2015 17:21:24 +0200 Subject: [PATCH 192/441] merge references tables (cherry picked from commit a97a632aa76c3b7499ae170b689676d2f32b8550) --- apps/opencs/model/tools/mergeoperation.cpp | 3 +- apps/opencs/model/tools/mergestages.cpp | 34 ++++++++++++++++++++++ apps/opencs/model/tools/mergestages.hpp | 18 +++++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp index 0f128e86e..f9a89aaa1 100644 --- a/apps/opencs/model/tools/mergeoperation.cpp +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -35,8 +35,9 @@ CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::Fr appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopicInfos)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournalInfos)); appendStage (new MergeRefIdsStage (mState)); + appendStage (new MergeReferencesStage (mState)); - /// \todo References, Land, LandTextures + /// \todo Land, LandTextures } void CSMTools::MergeOperation::setTarget (std::auto_ptr document) diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index d8bd93824..80f16ec14 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -1,6 +1,8 @@ #include "mergestages.hpp" +#include + #include "mergestate.hpp" #include "../doc/document.hpp" @@ -53,3 +55,35 @@ void CSMTools::MergeRefIdsStage::perform (int stage, CSMDoc::Messages& messages) mState.mSource.getData().getReferenceables().copyTo ( stage, mState.mTarget->getData().getReferenceables()); } + + +CSMTools::MergeReferencesStage::MergeReferencesStage (MergeState& state) +: mState (state) +{} + +int CSMTools::MergeReferencesStage::setup() +{ + mIndex.clear(); + return mState.mSource.getData().getReferences().getSize(); +} + +void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getReferences().getRecord (stage); + + if (!record.isDeleted()) + { + CSMWorld::CellRef ref = record.get(); + + ref.mOriginalCell = ref.mCell; + + ref.mRefNum.mIndex = mIndex[Misc::StringUtils::lowerCase (ref.mCell)]++; + ref.mRefNum.mContentFile = 0; + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &ref); + + mState.mTarget->getData().getReferences().appendRecord (newRecord); + } +} diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index 5c2243d43..e1cc17a3e 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -2,6 +2,7 @@ #define CSM_TOOLS_MERGESTAGES_H #include +#include #include @@ -69,7 +70,6 @@ namespace CSMTools target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_ModifiedOnly, 0, &record.get())); } - class MergeRefIdsStage : public CSMDoc::Stage { MergeState& mState; @@ -84,6 +84,22 @@ namespace CSMTools virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; + + class MergeReferencesStage : public CSMDoc::Stage + { + MergeState& mState; + std::map mIndex; + + public: + + MergeReferencesStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; } #endif From e5038cbece18f4be320dfdffddcd046fe97e29d4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 30 Aug 2015 11:08:56 +0200 Subject: [PATCH 193/441] fixed merge stage order; added proper initialisation (cherry picked from commit b7983d08ba3f238f0cddad467049c87b17a3745f) --- apps/opencs/model/tools/mergeoperation.cpp | 5 ++++- apps/opencs/model/tools/mergestages.cpp | 17 +++++++++++++++++ apps/opencs/model/tools/mergestages.hpp | 15 +++++++++++++++ apps/opencs/model/tools/mergestate.hpp | 2 ++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp index f9a89aaa1..89f22ef1a 100644 --- a/apps/opencs/model/tools/mergeoperation.cpp +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -9,7 +9,8 @@ CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding) : CSMDoc::Operation (CSMDoc::State_Merging, true), mState (document) { - appendStage (new FinishMergedDocumentStage (mState, encoding)); + appendStage (new StartMergeStage (mState)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGlobals)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGmsts)); appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSkills)); @@ -37,6 +38,8 @@ CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::Fr appendStage (new MergeRefIdsStage (mState)); appendStage (new MergeReferencesStage (mState)); + appendStage (new FinishMergedDocumentStage (mState, encoding)); + /// \todo Land, LandTextures } diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 80f16ec14..349cc2925 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -8,6 +8,23 @@ #include "../doc/document.hpp" #include "../world/data.hpp" + +CSMTools::StartMergeStage::StartMergeStage (MergeState& state) +: mState (state) +{} + +int CSMTools::StartMergeStage::setup() +{ + return 1; +} + +void CSMTools::StartMergeStage::perform (int stage, CSMDoc::Messages& messages) +{ + mState.mCompleted = false; + mState.mTextureIndices.clear(); +} + + CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding) : mState (state), mEncoder (encoding) {} diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index e1cc17a3e..7d62058f0 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -14,6 +14,21 @@ namespace CSMTools { + class StartMergeStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + StartMergeStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + class FinishMergedDocumentStage : public CSMDoc::Stage { MergeState& mState; diff --git a/apps/opencs/model/tools/mergestate.hpp b/apps/opencs/model/tools/mergestate.hpp index 4482ba6f4..077edc9d9 100644 --- a/apps/opencs/model/tools/mergestate.hpp +++ b/apps/opencs/model/tools/mergestate.hpp @@ -2,6 +2,7 @@ #define CSM_TOOLS_MERGESTATE_H #include +#include #include "../doc/document.hpp" @@ -12,6 +13,7 @@ namespace CSMTools std::auto_ptr mTarget; CSMDoc::Document& mSource; bool mCompleted; + std::map, int> mTextureIndices; // (texture, content file) -> new texture MergeState (CSMDoc::Document& source) : mSource (source), mCompleted (false) {} }; From a2d4957d2af1b18147f7c563b3b764b01d2ebc2c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 30 Aug 2015 14:27:22 +0200 Subject: [PATCH 194/441] merge land texture tables (cherry picked from commit 890bbb6b119bab1b9a7b5724ab3e6a769ede761a) --- apps/opencs/model/tools/mergeoperation.cpp | 5 +- apps/opencs/model/tools/mergestages.cpp | 79 ++++++++++++++++++++++ apps/opencs/model/tools/mergestages.hpp | 31 +++++++++ apps/opencs/model/tools/mergestate.hpp | 4 +- apps/opencs/model/world/data.cpp | 5 ++ apps/opencs/model/world/data.hpp | 2 + 6 files changed, 124 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp index 89f22ef1a..9e791683c 100644 --- a/apps/opencs/model/tools/mergeoperation.cpp +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -37,10 +37,13 @@ CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::Fr appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournalInfos)); appendStage (new MergeRefIdsStage (mState)); appendStage (new MergeReferencesStage (mState)); + appendStage (new MergeReferencesStage (mState)); + appendStage (new ListLandTexturesMergeStage (mState)); + appendStage (new MergeLandTexturesStage (mState)); appendStage (new FinishMergedDocumentStage (mState, encoding)); - /// \todo Land, LandTextures + /// \todo Land } void CSMTools::MergeOperation::setTarget (std::auto_ptr document) diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 349cc2925..f34ba8eb4 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -1,6 +1,8 @@ #include "mergestages.hpp" +#include + #include #include "mergestate.hpp" @@ -104,3 +106,80 @@ void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messa mState.mTarget->getData().getReferences().appendRecord (newRecord); } } + + +CSMTools::ListLandTexturesMergeStage::ListLandTexturesMergeStage (MergeState& state) +: mState (state) +{} + +int CSMTools::ListLandTexturesMergeStage::setup() +{ + return mState.mSource.getData().getLand().getSize(); +} + +void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getLand().getRecord (stage); + + if (!record.isDeleted()) + { + ESM::Land& land = *record.get().mLand; + + // make sure record is loaded + land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | + ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); + + if (land.mLandData) + { + // list texture indices + std::pair key; + key.second = land.mPlugin; + + for (int i=0; imTextures[i]; + + mState.mTextureIndices[key] = -1; + } + } + } +} + + +CSMTools::MergeLandTexturesStage::MergeLandTexturesStage (MergeState& state) +: mState (state), mNext (mState.mTextureIndices.end()) +{} + +int CSMTools::MergeLandTexturesStage::setup() +{ + mNext = mState.mTextureIndices.begin(); + return mState.mTextureIndices.size(); +} + +void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& messages) +{ + mNext->second = stage; + + std::ostringstream stream; + stream << mNext->first.first << "_" << mNext->first.second; + + int index = mState.mSource.getData().getLandTextures().searchId (stream.str()); + + if (index!=-1) + { + CSMWorld::LandTexture texture = + mState.mSource.getData().getLandTextures().getRecord (index).get(); + + texture.mIndex = mNext->second; + texture.mId = stream.str(); + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture); + + mState.mTarget->getData().getLandTextures().appendRecord (newRecord); + } + /// \todo deal with missing textures (either abort merge or report and make sure OpenMW can deal with missing textures) + + ++mNext; +} diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index 7d62058f0..f0ae99842 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -115,6 +115,37 @@ namespace CSMTools virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; + + class ListLandTexturesMergeStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + ListLandTexturesMergeStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class MergeLandTexturesStage : public CSMDoc::Stage + { + MergeState& mState; + std::map, int>::iterator mNext; + + public: + + MergeLandTexturesStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; } #endif diff --git a/apps/opencs/model/tools/mergestate.hpp b/apps/opencs/model/tools/mergestate.hpp index 077edc9d9..29e1bbda7 100644 --- a/apps/opencs/model/tools/mergestate.hpp +++ b/apps/opencs/model/tools/mergestate.hpp @@ -1,6 +1,8 @@ #ifndef CSM_TOOLS_MERGESTATE_H #define CSM_TOOLS_MERGESTATE_H +#include + #include #include @@ -13,7 +15,7 @@ namespace CSMTools std::auto_ptr mTarget; CSMDoc::Document& mSource; bool mCompleted; - std::map, int> mTextureIndices; // (texture, content file) -> new texture + std::map, int> mTextureIndices; // (texture, content file) -> new texture MergeState (CSMDoc::Document& source) : mSource (source), mCompleted (false) {} }; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index b5722ab57..c78cc383c 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -878,6 +878,11 @@ const CSMWorld::IdCollection& CSMWorld::Data::getLandText return mLandTextures; } +CSMWorld::IdCollection& CSMWorld::Data::getLandTextures() +{ + return mLandTextures; +} + const CSMWorld::IdCollection& CSMWorld::Data::getSoundGens() const { return mSoundGens; diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index c9fe956b1..97be947dc 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -226,6 +226,8 @@ namespace CSMWorld const IdCollection& getLandTextures() const; + IdCollection& getLandTextures(); + const IdCollection& getSoundGens() const; IdCollection& getSoundGens(); From 35c4af9476a0ddc06eaa9058b466699e57f34fb9 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 31 Aug 2015 11:06:32 +0200 Subject: [PATCH 195/441] added copy constructor and assignment operator for Land record struct (cherry picked from commit b0641934d4a34b4e462932aa715cc0ca9d854cae) --- components/esm/loadland.cpp | 27 +++++++++++++++++++++++++++ components/esm/loadland.hpp | 8 ++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index b0897ec67..baeca34de 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -1,5 +1,7 @@ #include "loadland.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -215,4 +217,29 @@ bool Land::isDataLoaded(int flags) const return (mDataLoaded & flags) == (flags & mDataTypes); } + Land::Land (const Land& land) + : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), + mEsm (land.mEsm), mContext (land.mContext), mDataTypes (land.mDataTypes), + mDataLoaded (land.mDataLoaded), + mLandData (land.mLandData ? new LandData (*land.mLandData) : 0) + {} + + Land& Land::operator= (Land land) + { + swap (land); + return *this; + } + + void Land::swap (Land& land) + { + std::swap (mFlags, land.mFlags); + std::swap (mX, land.mX); + std::swap (mY, land.mY); + std::swap (mPlugin, land.mPlugin); + std::swap (mEsm, land.mEsm); + std::swap (mContext, land.mContext); + std::swap (mDataTypes, land.mDataTypes); + std::swap (mDataLoaded, land.mDataLoaded); + std::swap (mLandData, land.mLandData); + } } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 7dc5e8adb..b3d27293a 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -116,9 +116,13 @@ struct Land /// @note We only check data types that *can* be loaded (present in mDataTypes) bool isDataLoaded(int flags) const; + Land (const Land& land); + + Land& operator= (Land land); + + void swap (Land& land); + private: - Land(const Land& land); - Land& operator=(const Land& land); /// Loads data and marks it as loaded /// \return true if data is actually loaded from file, false otherwise From 8cb1f4ffe9cf95acaad3b6136847505212c77330 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 31 Aug 2015 11:10:58 +0200 Subject: [PATCH 196/441] additional safety check for land texture listing merge stage (cherry picked from commit 69045d7ec9d293f2bb5626b2ac4a8517a0cf67e4) --- apps/opencs/model/tools/mergestages.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index f34ba8eb4..a4072e4bc 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -130,7 +130,7 @@ void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); - if (land.mLandData) + if (land.mLandData && land.mDataLoaded & ESM::Land::DATA_VTEX) { // list texture indices std::pair key; From 44020df65f29551dd50956c39902709fa08c5a72 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 31 Aug 2015 14:17:11 +0200 Subject: [PATCH 197/441] made return type of ESMTerrain::Storage::getLand const (cherry picked from commit febf611c827a257a4d4000b601f9ef413f2f8012) --- apps/opencs/view/render/terrainstorage.cpp | 2 +- apps/opencs/view/render/terrainstorage.hpp | 2 +- apps/openmw/mwrender/terrainstorage.cpp | 2 +- apps/openmw/mwrender/terrainstorage.hpp | 2 +- components/esmterrain/storage.cpp | 10 +++++----- components/esmterrain/storage.hpp | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 9998daeee..da962313a 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -8,7 +8,7 @@ namespace CSVRender { } - ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + const ESM::Land* TerrainStorage::getLand(int cellX, int cellY) { std::ostringstream stream; stream << "#" << cellX << " " << cellY; diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 97782ad17..16b0f3ec7 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -18,7 +18,7 @@ namespace CSVRender private: const CSMWorld::Data& mData; - virtual ESM::Land* getLand (int cellX, int cellY); + virtual const ESM::Land* getLand (int cellX, int cellY); virtual const ESM::LandTexture* getLandTexture(int index, short plugin); virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 8ad2ea321..2808187a1 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -50,7 +50,7 @@ namespace MWRender maxY += 1; } - ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + const ESM::Land* TerrainStorage::getLand(int cellX, int cellY) { const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index e6f4a04ad..4aa5a188e 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -10,7 +10,7 @@ namespace MWRender class TerrainStorage : public ESMTerrain::Storage { private: - virtual ESM::Land* getLand (int cellX, int cellY); + virtual const ESM::Land* getLand (int cellX, int cellY); virtual const ESM::LandTexture* getLandTexture(int index, short plugin); public: diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 763f0f4e0..20692f53a 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -72,7 +72,7 @@ namespace ESMTerrain --cellX; row += ESM::Land::LAND_SIZE-1; } - ESM::Land* land = getLand(cellX, cellY); + const ESM::Land* land = getLand(cellX, cellY); if (land && land->mDataTypes&ESM::Land::DATA_VNML) { normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; @@ -107,7 +107,7 @@ namespace ESMTerrain ++cellX; row = 0; } - ESM::Land* land = getLand(cellX, cellY); + const ESM::Land* land = getLand(cellX, cellY); if (land && land->mDataTypes&ESM::Land::DATA_VCLR) { color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; @@ -156,7 +156,7 @@ namespace ESMTerrain float vertX_ = 0; // of current cell corner for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) { - ESM::Land* land = getLand(cellX, cellY); + const ESM::Land* land = getLand(cellX, cellY); if (land && !(land->mDataTypes&ESM::Land::DATA_VHGT)) land = NULL; @@ -261,7 +261,7 @@ namespace ESMTerrain assert(xmDataTypes&ESM::Land::DATA_VTEX)) { int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; @@ -382,7 +382,7 @@ namespace ESMTerrain int cellX = static_cast(std::floor(worldPos.x / 8192.f)); int cellY = static_cast(std::floor(worldPos.y / 8192.f)); - ESM::Land* land = getLand(cellX, cellY); + const ESM::Land* land = getLand(cellX, cellY); if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) return -2048; diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index e184cbc4c..698bcf99c 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -16,7 +16,7 @@ namespace ESMTerrain private: // Not implemented in this class, because we need different Store implementations for game and editor - virtual ESM::Land* getLand (int cellX, int cellY) = 0; + virtual const ESM::Land* getLand (int cellX, int cellY) = 0; virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; public: From 4f24c6a7c88ea47b30867175887b64e324f1337a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 1 Nov 2015 17:07:40 +1100 Subject: [PATCH 198/441] more const-ness fixes --- apps/opencs/view/world/physicssystem.cpp | 2 +- apps/opencs/view/world/physicssystem.hpp | 2 +- apps/openmw/mwworld/physicssystem.cpp | 2 +- apps/openmw/mwworld/physicssystem.hpp | 2 +- libs/openengine/bullet/physic.cpp | 2 +- libs/openengine/bullet/physic.hpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index 891b175d5..7dce611c4 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -181,7 +181,7 @@ namespace CSVWorld } void PhysicsSystem::addHeightField(Ogre::SceneManager *sceneManager, - float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts) + const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts) { std::string name = "HeightField_" + QString::number(x).toStdString() + "_" + QString::number(y).toStdString(); diff --git a/apps/opencs/view/world/physicssystem.hpp b/apps/opencs/view/world/physicssystem.hpp index 0036bf769..17661caa8 100644 --- a/apps/opencs/view/world/physicssystem.hpp +++ b/apps/opencs/view/world/physicssystem.hpp @@ -63,7 +63,7 @@ namespace CSVWorld void moveSceneNodes(const std::string sceneNodeName, const Ogre::Vector3 &position); void addHeightField(Ogre::SceneManager *sceneManager, - float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts); + const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts); void removeHeightField(Ogre::SceneManager *sceneManager, int x, int y); diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index bec4c6db3..50212afea 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -660,7 +660,7 @@ namespace MWWorld return MovementSolver::traceDown(ptr, mEngine, maxHeight); } - void PhysicsSystem::addHeightField (float* heights, + void PhysicsSystem::addHeightField (const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts) { diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index c1046aacb..21d712109 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -42,7 +42,7 @@ namespace MWWorld void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - void addHeightField (float* heights, + void addHeightField (const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts); diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 15be7665a..44a928121 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -363,7 +363,7 @@ namespace Physic //delete BulletShapeManager::getSingletonPtr(); } - void PhysicEngine::addHeightField(float* heights, + void PhysicEngine::addHeightField(const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts) { diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 7784e8941..2c7ac8f05 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -227,7 +227,7 @@ namespace Physic /** * Add a HeightField to the simulation */ - void addHeightField(float* heights, + void addHeightField(const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts); From c4b34a077ebd118606b6a09d516162000ebd5140 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 31 Aug 2015 16:13:26 +0200 Subject: [PATCH 199/441] refactored loading of land data (cherry picked from commit 69b9eadb52cf24398d64b6399341d9e6b5d40bdf) Conflicts: apps/openmw/mwworld/scene.cpp components/esmterrain/storage.cpp --- apps/esmtool/record.cpp | 16 ++-- apps/opencs/model/doc/savingstages.cpp | 5 +- apps/opencs/model/tools/mergestages.cpp | 4 +- apps/openmw/mwrender/globalmap.cpp | 8 +- apps/openmw/mwworld/scene.cpp | 14 +--- components/esm/loadland.cpp | 22 +++++- components/esm/loadland.hpp | 23 ++++-- components/esmterrain/storage.cpp | 97 ++++++++++++++----------- components/esmterrain/storage.hpp | 7 +- 9 files changed, 112 insertions(+), 84 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 2ee6c54bb..76c14e728 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -841,19 +841,13 @@ void Record::print() std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl; std::cout << " DataTypes: " << mData.mDataTypes << std::endl; - // Seems like this should done with reference counting in the - // loader to me. But I'm not really knowledgable about this - // record type yet. --Cory - bool wasLoaded = (mData.mDataLoaded != 0); - if (mData.mDataTypes) mData.loadData(mData.mDataTypes); - if (mData.mDataLoaded) - { - std::cout << " Height Offset: " << mData.mLandData->mHeightOffset << std::endl; + if (const ESM::Land::LandData *data = mData.getLandData (mData.mDataTypes)) + { + std::cout << " Height Offset: " << data->mHeightOffset << std::endl; // Lots of missing members. - std::cout << " Unknown1: " << mData.mLandData->mUnk1 << std::endl; - std::cout << " Unknown2: " << mData.mLandData->mUnk2 << std::endl; + std::cout << " Unknown1: " << data->mUnk1 << std::endl; + std::cout << " Unknown2: " << data->mUnk2 << std::endl; } - if (!wasLoaded) mData.unloadData(); } template<> diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 01d260d68..62ac48805 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -420,8 +420,9 @@ void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) mState.getWriter().startRecord (record.mLand->sRecordId); record.mLand->save (mState.getWriter()); - if(record.mLand->mLandData) - record.mLand->mLandData->save (mState.getWriter()); + + if (const ESM::Land::LandData *data = record.mLand->getLandData (record.mLand->mDataTypes)) + data->save (mState.getWriter()); mState.getWriter().endRecord (record.mLand->sRecordId); } diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index a4072e4bc..5bf0241ac 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -130,7 +130,7 @@ void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); - if (land.mLandData && land.mDataLoaded & ESM::Land::DATA_VTEX) + if (const ESM::Land::LandData *data = land.getLandData (ESM::Land::DATA_VTEX)) { // list texture indices std::pair key; @@ -138,7 +138,7 @@ void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& for (int i=0; imTextures[i]; + key.first = data->mTextures[i]; mState.mTextureIndices[key] = -1; } diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 90cf27049..2a1556c7c 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -83,6 +83,9 @@ namespace MWRender land->loadData(mask); } + const ESM::Land::LandData *landData = + land ? land->getLandData (ESM::Land::DATA_WNAM) : 0; + for (int cellY=0; cellY(float(cellX)/float(mCellSize) * 9); int vertexY = static_cast(float(cellY) / float(mCellSize) * 9); - int texelX = (x-mMinX) * mCellSize + cellX; int texelY = (mHeight-1) - ((y-mMinY) * mCellSize + cellY); unsigned char r,g,b; float y = 0; - if (land && land->mDataTypes & ESM::Land::DATA_WNAM) - y = (land->mLandData->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f; + if (landData) + y = (landData->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f; else y = (SCHAR_MIN << 4) / 2048.f; if (y < 0) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index f5a9b8960..c3daafb62 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -236,16 +236,10 @@ namespace MWWorld // Actually only VHGT is needed here, but we'll need the rest for rendering anyway. // Load everything now to reduce IO overhead. const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX; - if (!land->isDataLoaded(flags)) - land->loadData(flags); - mPhysics->addHeightField ( - land->mLandData->mHeights, - cell->getCell()->getGridX(), - cell->getCell()->getGridY(), - 0, - worldsize / (verts-1), - verts) - ; + + const ESM::Land::LandData *data = land->getLandData (flags); + mPhysics->addHeightField (data->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(), + 0, worldsize / (verts-1), verts); } } diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index baeca34de..770830fdd 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -10,7 +10,7 @@ namespace ESM { unsigned int Land::sRecordId = REC_LAND; -void Land::LandData::save(ESMWriter &esm) +void Land::LandData::save(ESMWriter &esm) const { if (mDataTypes & Land::DATA_VNML) { esm.writeHNT("VNML", mNormals, sizeof(mNormals)); @@ -55,7 +55,7 @@ void Land::LandData::save(ESMWriter &esm) } } -void Land::LandData::transposeTextureData(uint16_t *in, uint16_t *out) +void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) { int readPos = 0; //bit ugly, but it works for ( int y1 = 0; y1 < 4; y1++ ) @@ -139,7 +139,7 @@ void Land::save(ESMWriter &esm) const esm.writeHNT("DATA", mFlags); } -void Land::loadData(int flags) +void Land::loadData(int flags) const { // Try to load only available data flags = flags & mDataTypes; @@ -201,7 +201,7 @@ void Land::unloadData() } } -bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) +bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const { if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { mEsm->getHExact(ptr, size); @@ -242,4 +242,18 @@ bool Land::isDataLoaded(int flags) const std::swap (mDataLoaded, land.mDataLoaded); std::swap (mLandData, land.mLandData); } + + const Land::LandData *Land::getLandData (int flags) const + { + if (!(flags & mDataTypes)) + return 0; + + loadData (flags); + return mLandData; + } + + const Land::LandData *Land::getLandData() const + { + return mLandData; + } } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index b3d27293a..78646a534 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -35,7 +35,6 @@ struct Land ESM_Context mContext; int mDataTypes; - int mDataLoaded; enum { @@ -91,12 +90,10 @@ struct Land short mUnk1; uint8_t mUnk2; - void save(ESMWriter &esm); - static void transposeTextureData(uint16_t *in, uint16_t *out); + void save(ESMWriter &esm) const; + static void transposeTextureData(const uint16_t *in, uint16_t *out); }; - LandData *mLandData; - void load(ESMReader &esm); void save(ESMWriter &esm) const; @@ -105,7 +102,7 @@ struct Land /** * Actually loads data */ - void loadData(int flags); + void loadData(int flags) const; /** * Frees memory allocated for land data @@ -122,12 +119,24 @@ struct Land void swap (Land& land); + /// Return land data with at least the data types specified in \a flags loaded (if they + /// are available). Will return a 0-pointer if there is no data for any of the + /// specified types. + const LandData *getLandData (int flags) const; + + /// Return land data without loading first anything. Can return a 0-pointer. + const LandData *getLandData() const; + private: /// Loads data and marks it as loaded /// \return true if data is actually loaded from file, false otherwise /// including the case when data is already loaded - bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size); + bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const; + + mutable int mDataLoaded; + + mutable LandData *mLandData; }; } diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 20692f53a..83fa85890 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -16,6 +16,14 @@ namespace ESMTerrain { + const ESM::Land::LandData *Storage::getLandData (int cellX, int cellY, int flags) + { + if (const ESM::Land *land = getLand (cellX, cellY)) + return land->getLandData (flags); + + return 0; + } + bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) { assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); @@ -30,24 +38,25 @@ namespace ESMTerrain int cellX = static_cast(origin.x); int cellY = static_cast(origin.y); - const ESM::Land* land = getLand(cellX, cellY); - if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) - return false; - - min = std::numeric_limits::max(); - max = -std::numeric_limits::max(); - for (int row=0; row::max(); + max = -std::numeric_limits::max(); + for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; - if (h > max) - max = h; - if (h < min) - min = h; + for (int col=0; colmHeights[col*ESM::Land::LAND_SIZE+row]; + if (h > max) + max = h; + if (h < min) + min = h; + } } + return true; } - return true; + + return false; } void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) @@ -72,12 +81,12 @@ namespace ESMTerrain --cellX; row += ESM::Land::LAND_SIZE-1; } - const ESM::Land* land = getLand(cellX, cellY); - if (land && land->mDataTypes&ESM::Land::DATA_VNML) + + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VNML)) { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.x = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; normal.normalise(); } else @@ -107,12 +116,12 @@ namespace ESMTerrain ++cellX; row = 0; } - const ESM::Land* land = getLand(cellX, cellY); - if (land && land->mDataTypes&ESM::Land::DATA_VCLR) + + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VCLR)) { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + color.r = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; } else { @@ -156,9 +165,9 @@ namespace ESMTerrain float vertX_ = 0; // of current cell corner for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) { - const ESM::Land* land = getLand(cellX, cellY); - if (land && !(land->mDataTypes&ESM::Land::DATA_VHGT)) - land = NULL; + const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT); + const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML); + const ESM::Land::LandData *colourData = getLandData (cellX, cellY, ESM::Land::DATA_VCLR); int rowStart = 0; int colStart = 0; @@ -177,16 +186,17 @@ namespace ESMTerrain { positions[static_cast(vertX*numVerts * 3 + vertY * 3)] = ((vertX / float(numVerts - 1) - 0.5f) * size * 8192); positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 1)] = ((vertY / float(numVerts - 1) - 0.5f) * size * 8192); - if (land) - positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 2)] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE + row]; - else - positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 2)] = -2048; - if (land && land->mDataTypes&ESM::Land::DATA_VNML) + float height = -2048; + if (heightData) + height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; + positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 2)] = height; + + if (normalData) { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.x = normalData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = normalData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = normalData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; normal.normalise(); } else @@ -206,11 +216,11 @@ namespace ESMTerrain normals[static_cast(vertX*numVerts * 3 + vertY * 3 + 1)] = normal.y; normals[static_cast(vertX*numVerts * 3 + vertY * 3 + 2)] = normal.z; - if (land && land->mDataTypes&ESM::Land::DATA_VCLR) + if (colourData) { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + color.r = colourData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = colourData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = colourData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; } else { @@ -261,13 +271,12 @@ namespace ESMTerrain assert(xmDataTypes&ESM::Land::DATA_VTEX)) + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VTEX)) { - int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + int tex = data->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; if (tex == 0) return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin - return std::make_pair(tex, land->mPlugin); + return std::make_pair(tex, getLand (cellX, cellY)->mPlugin); } else return std::make_pair(0,0); @@ -459,7 +468,7 @@ namespace ESMTerrain { assert(x < ESM::Land::LAND_SIZE); assert(y < ESM::Land::LAND_SIZE); - return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; + return land->getLandData()->mHeights[y * ESM::Land::LAND_SIZE + x]; } Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 698bcf99c..af1b0d5c3 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -16,11 +16,16 @@ namespace ESMTerrain private: // Not implemented in this class, because we need different Store implementations for game and editor - virtual const ESM::Land* getLand (int cellX, int cellY) = 0; + virtual const ESM::Land* getLand (int cellX, int cellY)= 0; virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; public: + /// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for + /// any of the data types specified via \a flags. Will also return a 0-pointer if there + /// is no land record for the coordinates \a cellX / \a cellY. + const ESM::Land::LandData *getLandData (int cellX, int cellY, int flags); + // Not implemented in this class, because we need different Store implementations for game and editor /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; From ce34daa64b679ce870623f959748236765cd3df8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 31 Aug 2015 18:13:27 +0200 Subject: [PATCH 200/441] removed indirection in OpenMW-CS land record (cherry picked from commit 85f6bb892b7f61b95d297efcf67cfccaee134e54) Conflicts: apps/opencs/view/render/cell.cpp --- apps/opencs/model/doc/savingstages.cpp | 10 +++++----- apps/opencs/model/tools/mergestages.cpp | 2 +- apps/opencs/model/world/data.cpp | 7 ++++++- apps/opencs/model/world/data.hpp | 2 ++ apps/opencs/model/world/land.cpp | 16 ++-------------- apps/opencs/model/world/land.hpp | 10 ++-------- apps/opencs/view/render/cell.cpp | 18 ++++++++++-------- apps/opencs/view/render/terrainstorage.cpp | 7 +++---- 8 files changed, 31 insertions(+), 41 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 62ac48805..c6d8a8cb3 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -415,16 +415,16 @@ void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) if (land.mState==CSMWorld::RecordBase::State_Modified || land.mState==CSMWorld::RecordBase::State_ModifiedOnly) { - CSMWorld::Land record = land.get(); + const CSMWorld::Land& record = land.get(); - mState.getWriter().startRecord (record.mLand->sRecordId); + mState.getWriter().startRecord (record.sRecordId); - record.mLand->save (mState.getWriter()); + record.save (mState.getWriter()); - if (const ESM::Land::LandData *data = record.mLand->getLandData (record.mLand->mDataTypes)) + if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes)) data->save (mState.getWriter()); - mState.getWriter().endRecord (record.mLand->sRecordId); + mState.getWriter().endRecord (record.sRecordId); } else if (land.mState==CSMWorld::RecordBase::State_Deleted) { diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 5bf0241ac..c624ff548 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -124,7 +124,7 @@ void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& if (!record.isDeleted()) { - ESM::Land& land = *record.get().mLand; + const ESM::Land& land = record.get(); // make sure record is loaded land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index c78cc383c..9f48bcfdf 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -873,6 +873,11 @@ const CSMWorld::IdCollection& CSMWorld::Data::getLand() const return mLand; } +CSMWorld::IdCollection& CSMWorld::Data::getLand() +{ + return mLand; +} + const CSMWorld::IdCollection& CSMWorld::Data::getLandTextures() const { return mLandTextures; @@ -1051,7 +1056,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) int index = mLand.load(*mReader, mBase); if (index!=-1 && !mBase) - mLand.getRecord (index).mModified.mLand->loadData ( + mLand.getRecord (index).mModified.loadData ( ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 97be947dc..944a636f0 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -224,6 +224,8 @@ namespace CSMWorld const IdCollection& getLand() const; + IdCollection& getLand(); + const IdCollection& getLandTextures() const; IdCollection& getLandTextures(); diff --git a/apps/opencs/model/world/land.cpp b/apps/opencs/model/world/land.cpp index 119e18761..222f9bc02 100644 --- a/apps/opencs/model/world/land.cpp +++ b/apps/opencs/model/world/land.cpp @@ -4,25 +4,13 @@ namespace CSMWorld { - - Land::Land() - { - mLand.reset(new ESM::Land()); - } - void Land::load(ESM::ESMReader &esm) { - mLand->load(esm); + ESM::Land::load(esm); std::ostringstream stream; - stream << "#" << mLand->mX << " " << mLand->mY; + stream << "#" << mX << " " << mY; mId = stream.str(); } - - void Land::blank() - { - /// \todo - } - } diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index e97a2d7dd..22cedb56d 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -2,7 +2,7 @@ #define CSM_WORLD_LAND_H #include -#include + #include namespace CSMWorld @@ -11,18 +11,12 @@ namespace CSMWorld /// /// \todo Add worldspace support to the Land record. /// \todo Add a proper copy constructor (currently worked around using shared_ptr) - struct Land + struct Land : public ESM::Land { - Land(); - - boost::shared_ptr mLand; - std::string mId; /// Loads the metadata and ID void load (ESM::ESMReader &esm); - - void blank(); }; } diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 156d9728b..c6da1bef8 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -33,7 +33,7 @@ bool CSVRender::Cell::addObjects (int start, int end) bool modified = false; const CSMWorld::RefCollection& collection = mData.getReferences(); - + for (int i=start; i<=end; ++i) { std::string cell = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mCell); @@ -70,20 +70,22 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, int landIndex = land.searchId(mId); if (landIndex != -1) { - const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get(); - if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT) + const ESM::Land& esmLand = land.getRecord(mId).get(); + + if (esmLand.getLandData (ESM::Land::DATA_VHGT)) { mTerrain.reset(new Terrain::TerrainGrid(sceneManager, new TerrainStorage(mData), Element_Terrain, true, Terrain::Align_XY)); - mTerrain->loadCell(esmLand->mX, - esmLand->mY); + mTerrain->loadCell(esmLand.mX, + esmLand.mY); float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; - mX = esmLand->mX; - mY = esmLand->mY; + mX = esmLand.mX; + mY = esmLand.mY; + mPhysics->addHeightField(sceneManager, - esmLand->mLandData->mHeights, mX, mY, 0, worldsize / (verts-1), verts); + esmLand.getLandData(ESM::Land::DATA_VHGT)->mHeights, mX, mY, 0, worldsize / (verts-1), verts); } } } diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index da962313a..41fe70c01 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -19,11 +19,10 @@ namespace CSVRender if (index == -1) return NULL; - ESM::Land* land = mData.getLand().getRecord(index).get().mLand.get(); + const ESM::Land& land = mData.getLand().getRecord(index).get(); int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; - if (!land->isDataLoaded(mask)) - land->loadData(mask); - return land; + land.loadData (mask); + return &land; } const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) From 25952369aefe6908b2a59ca7bf60793c951d5e61 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 3 Sep 2015 16:15:00 +0200 Subject: [PATCH 201/441] merge land tables (cherry picked from commit a8dc1c119859abd0ad4cf16765abbf1f70fe6013) --- apps/opencs/model/tools/mergeoperation.cpp | 3 +- apps/opencs/model/tools/mergestages.cpp | 64 +++++++++++++++++++++- apps/opencs/model/tools/mergestages.hpp | 15 +++++ components/esm/loadland.cpp | 26 +++++++++ components/esm/loadland.hpp | 11 ++++ 5 files changed, 115 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp index 9e791683c..907d742ed 100644 --- a/apps/opencs/model/tools/mergeoperation.cpp +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -40,10 +40,9 @@ CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::Fr appendStage (new MergeReferencesStage (mState)); appendStage (new ListLandTexturesMergeStage (mState)); appendStage (new MergeLandTexturesStage (mState)); + appendStage (new MergeLandStage (mState)); appendStage (new FinishMergedDocumentStage (mState, encoding)); - - /// \todo Land } void CSMTools::MergeOperation::setTarget (std::auto_ptr document) diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index c624ff548..f4cb42aa4 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -153,12 +153,20 @@ CSMTools::MergeLandTexturesStage::MergeLandTexturesStage (MergeState& state) int CSMTools::MergeLandTexturesStage::setup() { - mNext = mState.mTextureIndices.begin(); - return mState.mTextureIndices.size(); + // Should use the size of mState.mTextureIndices instead, but that is not available at this + // point. Unless there are any errors in the land and land texture records this will not + // make a difference. + return mState.mSource.getData().getLandTextures().getSize(); } void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& messages) { + if (stage==0) + mNext = mState.mTextureIndices.begin(); + + if (mNext==mState.mTextureIndices.end()) + return; + mNext->second = stage; std::ostringstream stream; @@ -183,3 +191,55 @@ void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& mes ++mNext; } + + +CSMTools::MergeLandStage::MergeLandStage (MergeState& state) : mState (state) {} + +int CSMTools::MergeLandStage::setup() +{ + return mState.mSource.getData().getLand().getSize(); +} + +void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getLand().getRecord (stage); + + if (!record.isDeleted()) + { + const CSMWorld::Land& land = record.get(); + + land.loadData (ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | + ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); + + CSMWorld::Land newLand (land); + + newLand.mEsm = 0; // avoid potential dangling pointer (ESMReader isn't needed anyway, + // because record is already fully loaded) + newLand.mPlugin = 0; + + // adjust land texture references + if (ESM::Land::LandData *data = newLand.getLandData()) + { + std::pair key; + key.second = land.mPlugin; + + for (int i=0; imTextures[i]; + std::map, int>::const_iterator iter = + mState.mTextureIndices.find (key); + + if (iter!=mState.mTextureIndices.end()) + data->mTextures[i] = iter->second; + else + data->mTextures[i] = 0; + } + } + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &newLand); + + mState.mTarget->getData().getLand().appendRecord (newRecord); + } +} diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index f0ae99842..f88f5be9f 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -146,6 +146,21 @@ namespace CSMTools virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; + + class MergeLandStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + MergeLandStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; } #endif diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 770830fdd..784cfd407 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -256,4 +256,30 @@ bool Land::isDataLoaded(int flags) const { return mLandData; } + + Land::LandData *Land::getLandData() + { + return mLandData; + } + + void Land::add (int flags) + { + if (!mLandData) + mLandData = new LandData; + + mDataTypes |= flags; + mDataLoaded |= flags; + } + + void Land::remove (int flags) + { + mDataTypes &= ~flags; + mDataLoaded &= ~flags; + + if (!mDataLoaded) + { + delete mLandData; + mLandData = 0; + } + } } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 78646a534..ef7e677f5 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -127,6 +127,17 @@ struct Land /// Return land data without loading first anything. Can return a 0-pointer. const LandData *getLandData() const; + /// Return land data without loading first anything. Can return a 0-pointer. + LandData *getLandData(); + + /// \attention Must not be called on objects that aren't fully loaded. + /// + /// \note Added data fields will be uninitialised + void add (int flags); + + /// \attention Must not be called on objects that aren't fully loaded. + void remove (int flags); + private: /// Loads data and marks it as loaded From 1784701b47627f86ee376176a7445d671d27bffa Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 8 Sep 2015 15:33:15 +0200 Subject: [PATCH 202/441] fixed a texture indexing bug (only affects the editor) (cherry picked from commit 73f6efddcc1b6f5816f9f775418c6317cf74ee20) --- apps/opencs/model/tools/mergestages.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index f4cb42aa4..e24d1f911 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -179,6 +179,9 @@ void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& mes CSMWorld::LandTexture texture = mState.mSource.getData().getLandTextures().getRecord (index).get(); + std::ostringstream stream; + stream << mNext->second << "_0"; + texture.mIndex = mNext->second; texture.mId = stream.str(); From dcf1f1515a82afc5332ac21372bb027a962471fb Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 8 Sep 2015 16:01:34 +0200 Subject: [PATCH 203/441] handle missing land texture records properly during merge (cherry picked from commit 09ec60fe2a7b2cce91ee5f2271db4479b97ae228) --- apps/opencs/model/tools/mergestages.cpp | 47 ++++++++++++++----------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index e24d1f911..fab5fae71 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -164,35 +164,42 @@ void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& mes if (stage==0) mNext = mState.mTextureIndices.begin(); - if (mNext==mState.mTextureIndices.end()) - return; + bool found = false; - mNext->second = stage; + do + { + if (mNext==mState.mTextureIndices.end()) + return; - std::ostringstream stream; - stream << mNext->first.first << "_" << mNext->first.second; + mNext->second = stage; - int index = mState.mSource.getData().getLandTextures().searchId (stream.str()); + std::ostringstream stream; + stream << mNext->first.first << "_" << mNext->first.second; - if (index!=-1) - { - CSMWorld::LandTexture texture = - mState.mSource.getData().getLandTextures().getRecord (index).get(); + int index = mState.mSource.getData().getLandTextures().searchId (stream.str()); - std::ostringstream stream; - stream << mNext->second << "_0"; + if (index!=-1) + { + CSMWorld::LandTexture texture = + mState.mSource.getData().getLandTextures().getRecord (index).get(); - texture.mIndex = mNext->second; - texture.mId = stream.str(); + std::ostringstream stream; + stream << mNext->second << "_0"; - CSMWorld::Record newRecord ( - CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture); + texture.mIndex = mNext->second; + texture.mId = stream.str(); - mState.mTarget->getData().getLandTextures().appendRecord (newRecord); - } - /// \todo deal with missing textures (either abort merge or report and make sure OpenMW can deal with missing textures) + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture); + + mState.mTarget->getData().getLandTextures().appendRecord (newRecord); - ++mNext; + found = true; + } + + ++mNext; + } + while (!found); } From 4aa6d7d1d1b78f4891751eddff069418580db24f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 10 Sep 2015 12:41:31 +0200 Subject: [PATCH 204/441] various minor fixes (cherry picked from commit 584a7a66b97739c91c38cf8f17922ba29d5536e2) --- apps/opencs/model/tools/mergestages.cpp | 33 ++++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index fab5fae71..f7c3b8dc0 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -124,7 +124,7 @@ void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& if (!record.isDeleted()) { - const ESM::Land& land = record.get(); + const CSMWorld::Land& land = record.get(); // make sure record is loaded land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | @@ -228,22 +228,25 @@ void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) // because record is already fully loaded) newLand.mPlugin = 0; - // adjust land texture references - if (ESM::Land::LandData *data = newLand.getLandData()) + if (land.mDataTypes & ESM::Land::DATA_VTEX) { - std::pair key; - key.second = land.mPlugin; - - for (int i=0; imTextures[i]; - std::map, int>::const_iterator iter = - mState.mTextureIndices.find (key); - - if (iter!=mState.mTextureIndices.end()) - data->mTextures[i] = iter->second; - else - data->mTextures[i] = 0; + std::pair key; + key.second = land.mPlugin; + + for (int i=0; imTextures[i]; + std::map, int>::const_iterator iter = + mState.mTextureIndices.find (key); + + if (iter!=mState.mTextureIndices.end()) + data->mTextures[i] = iter->second; + else + data->mTextures[i] = 0; + } } } From 453c3ee7607d6fcc287a7134275c1802cc5964da Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 11 Sep 2015 10:50:19 +0200 Subject: [PATCH 205/441] fixed an indexing error (cherry picked from commit 23fde87816644912c9896920b80348429a045003) --- apps/opencs/model/tools/mergestages.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index f7c3b8dc0..52e1e6964 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -171,10 +171,10 @@ void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& mes if (mNext==mState.mTextureIndices.end()) return; - mNext->second = stage; + mNext->second = stage+1; std::ostringstream stream; - stream << mNext->first.first << "_" << mNext->first.second; + stream << mNext->first.first-1 << "_" << mNext->first.second; int index = mState.mSource.getData().getLandTextures().searchId (stream.str()); @@ -184,9 +184,9 @@ void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& mes mState.mSource.getData().getLandTextures().getRecord (index).get(); std::ostringstream stream; - stream << mNext->second << "_0"; + stream << mNext->second-1 << "_0"; - texture.mIndex = mNext->second; + texture.mIndex = mNext->second-1; texture.mId = stream.str(); CSMWorld::Record newRecord ( From 5031d1fbcba1a817da514113d1abfc56a9076017 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 11 Sep 2015 13:02:24 +0200 Subject: [PATCH 206/441] disable on-demand loading of land data (for now, maybe) (cherry picked from commit 5be176ee85da349163e161f86693f836fb1b5fdc) --- apps/opencs/model/world/data.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 9f48bcfdf..01596af7c 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1055,8 +1055,10 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) { int index = mLand.load(*mReader, mBase); - if (index!=-1 && !mBase) - mLand.getRecord (index).mModified.loadData ( + // Load all land data for now. A future optimisation may only load non-base data + // if a suitable mechanism for avoiding race conditions can be established. + if (index!=-1/* && !mBase*/) + mLand.getRecord (index).get().loadData ( ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); From 03ee1ec8cbbccf8e72e3aa759f35ea7f05cc67ad Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sun, 6 Sep 2015 21:46:05 +0200 Subject: [PATCH 207/441] adjust FindMyGUI.cmake to correctly handle REQUIRED and QUIETLY (cherry picked from commit b68f64ed97aff7c48d6878241ecd64d6acaba2f5) --- cmake/FindMyGUI.cmake | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index 2829a74d3..694cf3505 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -182,11 +182,12 @@ IF (MYGUI_FOUND) IF (NOT MYGUI_FIND_QUIETLY) MESSAGE(STATUS "MyGUI version: ${MYGUI_VERSION}") ENDIF (NOT MYGUI_FIND_QUIETLY) - -ELSE (MYGUI_FOUND) - IF (MYGUI_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find MYGUI") - ENDIF (MYGUI_FIND_REQUIRED) ENDIF (MYGUI_FOUND) +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(MyGUI DEFAULT_MSG + MYGUI_INCLUDE_DIRS + FREETYPE_LIBRARIES + MYGUI_LIBRARIES) + CMAKE_POLICY(POP) From c0f21bfb775f668f1567c77df10bd3b8166a9ac6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 11 Sep 2015 21:09:54 +0200 Subject: [PATCH 208/441] Add some comments to ESM::Land (cherry picked from commit 5252dbcf1f4dc311419ca9f7d5f32a765771d1e0) Conflicts: components/esm/loadland.hpp --- components/esm/loadland.hpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index ef7e677f5..8ec4f74ea 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -76,17 +76,29 @@ struct Land struct LandData { + // Initial reference height for the first vertex, only needed for filling mHeights float mHeightOffset; + // Height in world space for each vertex float mHeights[LAND_NUM_VERTS]; + + // 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage. VNML mNormals[LAND_NUM_VERTS * 3]; + + // 2D array of texture indices. An index can be used to look up an ESM::LandTexture, + // but to do so you must subtract 1 from the index first! + // An index of 0 indicates the default texture. uint16_t mTextures[LAND_NUM_TEXTURES]; - char mColours[3 * LAND_NUM_VERTS]; + // 24-bit RGB color for each vertex + unsigned char mColours[3 * LAND_NUM_VERTS]; + + // DataTypes available in this LandData, accessing data that is not available is an undefined operation int mDataTypes; // low-LOD heightmap (used for rendering the global map) signed char mWnam[81]; + // ??? short mUnk1; uint8_t mUnk2; From c40e68cc5b972fb72de0880137378890da180e47 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 14 Sep 2015 15:14:18 +0200 Subject: [PATCH 209/441] Add CONTRIBUTING.md (cherry picked from commit 6c6fc662ef80b5c99fc2b3723faa410edb37868c) --- CONTRIBUTING.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..879113a51 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +Description +=========== + +Your pull request description should include: + +* Summary of the changes made +* Reasoning / motivation behind the change +* What testing you have carried out to verify the change +* If applicable, a link back to the bug report or forum discussion that prompted the change + +Other notes +=========== + +* Separate your work into multiple pull requests whenever possible. As a rule of thumb, each feature and each bugfix should go into a separate PR, unless they are closely related or dependent upon each other. Small pull requests are easier to review, and are less likely to require further changes before we can merge them. A "mega" pull request with lots of unrelated commits in it is likely to get held up in review for a long time. +* Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title. +* If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards). From 7c347a590856dce3de6dc40e872f544dc8066382 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 14 Sep 2015 17:17:38 +0200 Subject: [PATCH 210/441] CONTRIBUTING.md: clarify "if applicable" (cherry picked from commit 5fde9149594f6503c192f9d949d8f0bb3f3df5ba) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 879113a51..a9cd6a690 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,12 @@ Description =========== -Your pull request description should include: +Your pull request description should include (if applicable): +* A link back to the bug report or forum discussion that prompted the change * Summary of the changes made * Reasoning / motivation behind the change * What testing you have carried out to verify the change -* If applicable, a link back to the bug report or forum discussion that prompted the change Other notes =========== From 496cdcb51b47784953cb11028b8449071cdcac25 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 12 Sep 2015 09:04:43 +1000 Subject: [PATCH 211/441] Ensure ColumnId names are unique. Filter parser calls Columns::getId() which implies that these should be unique. (cherry picked from commit 258b2ba29abedf329db1db2c5660cf213445c999) --- apps/opencs/model/world/columns.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index af26a6901..d6d2abe92 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -265,7 +265,7 @@ namespace CSMWorld { ColumnId_LevelledList,"Levelled List" }, { ColumnId_LevelledItemId,"Levelled Item" }, - { ColumnId_LevelledItemLevel,"Level" }, + { ColumnId_LevelledItemLevel,"Item Level" }, { ColumnId_LevelledItemType, "Calculate all levels <= player" }, { ColumnId_LevelledItemTypeEach, "Select a new item each instance" }, { ColumnId_LevelledItemChanceNone, "Chance None" }, @@ -281,26 +281,26 @@ namespace CSMWorld { ColumnId_InfoCondValue, "Values" }, { ColumnId_OriginalCell, "Original Cell" }, - { ColumnId_NpcAttributes, "Attributes" }, - { ColumnId_NpcSkills, "Skills" }, + { ColumnId_NpcAttributes, "NPC Attributes" }, + { ColumnId_NpcSkills, "NPC Skill" }, { ColumnId_UChar, "Value [0..255]" }, - { ColumnId_NpcMisc, "Misc" }, + { ColumnId_NpcMisc, "NPC Misc" }, { ColumnId_NpcLevel, "Level" }, { ColumnId_NpcFactionID, "Faction ID" }, - { ColumnId_NpcHealth, "Health" }, + { ColumnId_NpcHealth, "NPC Health" }, { ColumnId_NpcMana, "Mana" }, { ColumnId_NpcFatigue, "Fatigue" }, - { ColumnId_NpcDisposition, "Disposition" }, + { ColumnId_NpcDisposition, "NPC Disposition" }, { ColumnId_NpcReputation, "Reputation" }, - { ColumnId_NpcRank, "Rank" }, + { ColumnId_NpcRank, "NPC Rank" }, { ColumnId_NpcGold, "Gold" }, { ColumnId_NpcPersistence, "Persistent" }, - { ColumnId_RaceAttributes, "Attributes" }, - { ColumnId_RaceMaleValue, "Male" }, - { ColumnId_RaceFemaleValue, "Female" }, + { ColumnId_RaceAttributes, "Race Attributes" }, + { ColumnId_RaceMaleValue, "Male Attrib" }, + { ColumnId_RaceFemaleValue, "Female Attrib" }, { ColumnId_RaceSkillBonus, "Skill Bonus" }, - { ColumnId_RaceSkill, "Skills" }, + { ColumnId_RaceSkill, "Race Skill" }, { ColumnId_RaceBonus, "Bonus" }, { ColumnId_Interior, "Interior" }, From 41f2ffdebb9c70850bbf713ee99ea0fcd5a4f64d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 12 Sep 2015 09:33:11 +1000 Subject: [PATCH 212/441] Rationalise the use of ColumnIds (cherry picked from commit c4a900ca2c9b98267c9a507bb887829373fc9193) Conflicts: apps/opencs/model/world/refidcollection.cpp --- apps/opencs/model/world/columns.cpp | 15 +++++---------- apps/opencs/model/world/columns.hpp | 14 +++++++------- apps/opencs/model/world/data.cpp | 4 ++-- apps/opencs/model/world/nestedcoladapterimp.cpp | 2 +- apps/opencs/model/world/refidadapterimp.cpp | 15 ++------------- apps/opencs/model/world/refidcollection.cpp | 14 +++++++------- apps/opencs/view/doc/viewmanager.cpp | 1 - 7 files changed, 24 insertions(+), 41 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index d6d2abe92..563cc1615 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -199,8 +199,6 @@ namespace CSMWorld { ColumnId_RotY, "Rotation Y"}, { ColumnId_RotZ, "Rotation Z"}, - { ColumnId_Skill, "Skill" }, - { ColumnId_OwnerGlobal, "Owner Global" }, { ColumnId_DefaultProfile, "Default Profile" }, { ColumnId_BypassNewGame, "Bypass New Game" }, @@ -271,7 +269,7 @@ namespace CSMWorld { ColumnId_LevelledItemChanceNone, "Chance None" }, { ColumnId_PowerList, "Powers" }, - { ColumnId_SkillImpact, "Skills" }, + { ColumnId_SkillImpact, "Skill" }, { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, @@ -285,22 +283,20 @@ namespace CSMWorld { ColumnId_NpcSkills, "NPC Skill" }, { ColumnId_UChar, "Value [0..255]" }, { ColumnId_NpcMisc, "NPC Misc" }, - { ColumnId_NpcLevel, "Level" }, + { ColumnId_Level, "Level" }, { ColumnId_NpcFactionID, "Faction ID" }, - { ColumnId_NpcHealth, "NPC Health" }, - { ColumnId_NpcMana, "Mana" }, - { ColumnId_NpcFatigue, "Fatigue" }, + { ColumnId_Mana, "Mana" }, + { ColumnId_Fatigue, "Fatigue" }, { ColumnId_NpcDisposition, "NPC Disposition" }, { ColumnId_NpcReputation, "Reputation" }, { ColumnId_NpcRank, "NPC Rank" }, - { ColumnId_NpcGold, "Gold" }, + { ColumnId_Gold, "Gold" }, { ColumnId_NpcPersistence, "Persistent" }, { ColumnId_RaceAttributes, "Race Attributes" }, { ColumnId_RaceMaleValue, "Male Attrib" }, { ColumnId_RaceFemaleValue, "Female Attrib" }, { ColumnId_RaceSkillBonus, "Skill Bonus" }, - { ColumnId_RaceSkill, "Race Skill" }, { ColumnId_RaceBonus, "Bonus" }, { ColumnId_Interior, "Interior" }, @@ -583,7 +579,6 @@ namespace // FIXME: don't have dynamic value enum delegate, use Display_String for now //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp; - case CSMWorld::Columns::ColumnId_RaceSkill: return sSkills; default: return 0; } diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 61b796581..0d8064e6a 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -189,7 +189,7 @@ namespace CSMWorld ColumnId_RotX = 174, ColumnId_RotY = 175, ColumnId_RotZ = 176, - ColumnId_Skill = 177, + // unused ColumnId_OwnerGlobal = 178, ColumnId_DefaultProfile = 179, ColumnId_BypassNewGame = 180, @@ -276,22 +276,22 @@ namespace CSMWorld ColumnId_NpcSkills = 249, ColumnId_UChar = 250, ColumnId_NpcMisc = 251, - ColumnId_NpcLevel = 252, + ColumnId_Level = 252, ColumnId_NpcFactionID = 253, - ColumnId_NpcHealth = 254, - ColumnId_NpcMana = 255, - ColumnId_NpcFatigue = 256, + // unused + ColumnId_Mana = 255, + ColumnId_Fatigue = 256, ColumnId_NpcDisposition = 257, ColumnId_NpcReputation = 258, ColumnId_NpcRank = 259, - ColumnId_NpcGold = 260, + ColumnId_Gold = 260, ColumnId_NpcPersistence = 261, ColumnId_RaceAttributes = 262, ColumnId_RaceMaleValue = 263, ColumnId_RaceFemaleValue = 264, ColumnId_RaceSkillBonus = 265, - ColumnId_RaceSkill = 266, + // unused ColumnId_RaceBonus = 267, ColumnId_Interior = 268, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 01596af7c..fee1cf2b4 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -209,7 +209,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceAttributeAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceAttributes, ColumnBase::Display_String, + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute, ColumnBase::Flag_Dialogue, false)); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_RaceMaleValue, ColumnBase::Display_Integer)); @@ -220,7 +220,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceSkill, ColumnBase::Display_RaceSkill)); + new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_RaceBonus, ColumnBase::Display_Integer)); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 14a436c83..3ebc637b5 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -923,7 +923,7 @@ namespace CSMWorld switch (subColIndex) { - case 0: return QString(ESM::Attribute::sAttributeNames[subRowIndex].c_str()); + case 0: return subRowIndex; case 1: return race.mData.mAttributeValues[subRowIndex].mMale; case 2: return race.mData.mAttributeValues[subRowIndex].mFemale; default: throw std::runtime_error("Race Attribute subcolumn index out of range"); diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 16579c2c7..e4cf39603 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -744,18 +744,7 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * const ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; if (subColIndex == 0) - switch (subRowIndex) - { - case 0: return QString("Strength"); - case 1: return QString("Intelligence"); - case 2: return QString("Willpower"); - case 3: return QString("Agility"); - case 4: return QString("Speed"); - case 5: return QString("Endurance"); - case 6: return QString("Personality"); - case 7: return QString("Luck"); - default: return QVariant(); // throw an exception here? - } + return subRowIndex; else if (subColIndex == 1) if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { @@ -886,7 +875,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu throw std::runtime_error ("index out of range"); if (subColIndex == 0) - return QString(ESM::Skill::sSkillNames[subRowIndex].c_str()); + return subRowIndex; else if (subColIndex == 1) { if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 3644fc264..d29225d53 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -449,7 +449,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attrMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcAttributes, CSMWorld::ColumnBase::Display_String, false, false)); + new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_Integer)); @@ -461,7 +461,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcSkills, CSMWorld::ColumnBase::Display_String, false, false)); + new RefIdColumn (Columns::ColumnId_SkillImpact, CSMWorld::ColumnBase::Display_SkillImpact, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_Integer)); @@ -473,16 +473,16 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), miscMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer, + new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_Integer, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcFactionID, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcHealth, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcMana, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcFatigue, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcDisposition, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( @@ -490,7 +490,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcRank, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcGold, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcPersistence, CSMWorld::ColumnBase::Display_Boolean)); diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 25949ef15..4590a2dd0 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -104,7 +104,6 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_AiPackageType, CSMWorld::Columns::ColumnId_AiPackageType, false }, { CSMWorld::ColumnBase::Display_InfoCondFunc, CSMWorld::Columns::ColumnId_InfoCondFunc, false }, { CSMWorld::ColumnBase::Display_InfoCondComp, CSMWorld::Columns::ColumnId_InfoCondComp, false }, - { CSMWorld::ColumnBase::Display_RaceSkill, CSMWorld::Columns::ColumnId_RaceSkill, true }, }; for (std::size_t i=0; i Date: Sat, 12 Sep 2015 10:00:23 +1000 Subject: [PATCH 213/441] Add missing fields for editing creatures. - Should resolve resolve bugs #2878 (level), #2901 (gold) and #2889 (health). - Moved Soul, Combat, Magic and Stealth editing to dialogue only (to be consistent with editing NPCs) (cherry picked from commit 5a5e1a3b22e21d8fb847f4ab04497c2d4fdaf4c5) Conflicts: apps/opencs/model/world/columns.cpp apps/opencs/model/world/columns.hpp --- apps/opencs/model/world/columns.cpp | 8 + apps/opencs/model/world/columns.hpp | 19 +- apps/opencs/model/world/refidadapterimp.cpp | 313 ++++++++++++++++++-- apps/opencs/model/world/refidadapterimp.hpp | 98 +++++- apps/opencs/model/world/refidcollection.cpp | 64 +++- 5 files changed, 460 insertions(+), 42 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 563cc1615..398fe2918 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -285,6 +285,7 @@ namespace CSMWorld { ColumnId_NpcMisc, "NPC Misc" }, { ColumnId_Level, "Level" }, { ColumnId_NpcFactionID, "Faction ID" }, + { ColumnId_Mana, "Mana" }, { ColumnId_Fatigue, "Fatigue" }, { ColumnId_NpcDisposition, "NPC Disposition" }, @@ -311,6 +312,13 @@ namespace CSMWorld { ColumnId_FileDescription, "File Description" }, { ColumnId_Author, "Author" }, + { ColumnId_CreatureAttributes, "Creature Attributes" }, + { ColumnId_AttributeValue, "Attrib Value" }, + { ColumnId_CreatureAttack, "Creature Attack" }, + { ColumnId_MinAttack, "Min Attack" }, + { ColumnId_MaxAttack, "Max Attack" }, + { ColumnId_CreatureMisc, "Creature Misc" }, + { ColumnId_SpellSrc, "From Race" }, { ColumnId_SpellCost, "Cast Cost" }, { ColumnId_SpellChance, "Cast Chance" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 0d8064e6a..a8b8cf2d6 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -102,7 +102,7 @@ namespace CSMWorld ColumnId_OriginalCreature = 87, ColumnId_Biped = 88, ColumnId_HasWeapon = 89, - // unused + // used for SpellSrc ColumnId_Swims = 91, ColumnId_Flies = 92, ColumnId_Walks = 93, @@ -189,7 +189,7 @@ namespace CSMWorld ColumnId_RotX = 174, ColumnId_RotY = 175, ColumnId_RotZ = 176, - // unused + // used for SpellCost ColumnId_OwnerGlobal = 178, ColumnId_DefaultProfile = 179, ColumnId_BypassNewGame = 180, @@ -278,7 +278,7 @@ namespace CSMWorld ColumnId_NpcMisc = 251, ColumnId_Level = 252, ColumnId_NpcFactionID = 253, - // unused + // used for SpellChance ColumnId_Mana = 255, ColumnId_Fatigue = 256, ColumnId_NpcDisposition = 257, @@ -309,9 +309,16 @@ namespace CSMWorld ColumnId_MinMagnitude = 278, ColumnId_MaxMagnitude = 279, - ColumnId_SpellSrc = 280, - ColumnId_SpellCost = 281, - ColumnId_SpellChance = 282, + ColumnId_CreatureAttributes = 280, + ColumnId_AttributeValue = 281, + ColumnId_CreatureAttack = 282, + ColumnId_MinAttack = 283, + ColumnId_MaxAttack = 284, + ColumnId_CreatureMisc = 285, + + ColumnId_SpellSrc = 90, + ColumnId_SpellCost = 177, + ColumnId_SpellChance = 254, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index e4cf39603..3b0008d18 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -301,12 +301,11 @@ void CSMWorld::ContainerRefIdAdapter::setData (const RefIdColumn *column, RefIdD CSMWorld::CreatureColumns::CreatureColumns (const ActorColumns& actorColumns) : ActorColumns (actorColumns), mType(NULL), - mSoul(NULL), mScale(NULL), mOriginal(NULL), - mCombat(NULL), - mMagic(NULL), - mStealth(NULL) + mAttributes(NULL), + mAttacks(NULL), + mMisc(NULL) {} CSMWorld::CreatureRefIdAdapter::CreatureRefIdAdapter (const CreatureColumns& columns) @@ -322,23 +321,20 @@ QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, con if (column==mColumns.mType) return record.get().mData.mType; - if (column==mColumns.mSoul) - return record.get().mData.mSoul; - if (column==mColumns.mScale) return record.get().mScale; if (column==mColumns.mOriginal) return QString::fromUtf8 (record.get().mOriginal.c_str()); - if (column==mColumns.mCombat) - return static_cast (record.get().mData.mCombat); + if (column==mColumns.mAttributes) + return true; // Required to show nested tables in dialogue subview - if (column==mColumns.mMagic) - return static_cast (record.get().mData.mMagic); + if (column==mColumns.mAttacks) + return true; // Required to show nested tables in dialogue subview - if (column==mColumns.mStealth) - return static_cast (record.get().mData.mStealth); + if (column==mColumns.mMisc) + return true; // Required to show nested items in dialogue subview std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -359,18 +355,10 @@ void CSMWorld::CreatureRefIdAdapter::setData (const RefIdColumn *column, RefIdDa if (column==mColumns.mType) creature.mData.mType = value.toInt(); - else if (column==mColumns.mSoul) - creature.mData.mSoul = value.toInt(); else if (column==mColumns.mScale) creature.mScale = value.toFloat(); else if (column==mColumns.mOriginal) creature.mOriginal = value.toString().toUtf8().constData(); - else if (column==mColumns.mCombat) - creature.mData.mCombat = value.toInt(); - else if (column==mColumns.mMagic) - creature.mData.mMagic = value.toInt(); - else if (column==mColumns.mStealth) - creature.mData.mStealth = value.toInt(); else { std::map::const_iterator iter = @@ -1077,6 +1065,289 @@ int CSMWorld::NpcMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, return 1; // fixed at size 1 } +CSMWorld::CreatureAttributesRefIdAdapter::CreatureAttributesRefIdAdapter() +{} + +void CSMWorld::CreatureAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttributesRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttributesRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + // store the whole struct + creature.mData = + static_cast > &>(nestedTable).mNestedTable.at(0); + + record.setModified (creature); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttributesRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + if (subColIndex == 0) + return subRowIndex; + else if (subColIndex == 1) + switch (subRowIndex) + { + case 0: return creature.mData.mStrength; + case 1: return creature.mData.mIntelligence; + case 2: return creature.mData.mWillpower; + case 3: return creature.mData.mAgility; + case 4: return creature.mData.mSpeed; + case 5: return creature.mData.mEndurance; + case 6: return creature.mData.mPersonality; + case 7: return creature.mData.mLuck; + default: return QVariant(); // throw an exception here? + } + else + return QVariant(); // throw an exception here? +} + +void CSMWorld::CreatureAttributesRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + if (subColIndex == 1) + switch(subRowIndex) + { + case 0: creature.mData.mStrength = value.toInt(); break; + case 1: creature.mData.mIntelligence = value.toInt(); break; + case 2: creature.mData.mWillpower = value.toInt(); break; + case 3: creature.mData.mAgility = value.toInt(); break; + case 4: creature.mData.mSpeed = value.toInt(); break; + case 5: creature.mData.mEndurance = value.toInt(); break; + case 6: creature.mData.mPersonality = value.toInt(); break; + case 7: creature.mData.mLuck = value.toInt(); break; + default: return; // throw an exception here? + } + else + return; // throw an exception here? + + record.setModified (creature); +} + +int CSMWorld::CreatureAttributesRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 2; +} + +int CSMWorld::CreatureAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + // There are 8 attributes + return 8; +} + +CSMWorld::CreatureAttackRefIdAdapter::CreatureAttackRefIdAdapter() +{} + +void CSMWorld::CreatureAttackRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttackRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttackRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + // store the whole struct + creature.mData = + static_cast > &>(nestedTable).mNestedTable.at(0); + + record.setModified (creature); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttackRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + if (subRowIndex < 0 || subRowIndex > 2 || subColIndex < 0 || subColIndex > 2) + throw std::runtime_error ("index out of range"); + + if (subColIndex == 0) + return subRowIndex + 1; + else if (subColIndex < 3) // 1 or 2 + return creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)]; + else + return QVariant(); // throw an exception here? +} + +void CSMWorld::CreatureAttackRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + if (subRowIndex < 0 || subRowIndex > 2) + throw std::runtime_error ("index out of range"); + + if (subColIndex == 1 || subColIndex == 2) + creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)] = value.toInt(); + else + return; // throw an exception here? + + record.setModified (creature); +} + +int CSMWorld::CreatureAttackRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 3; +} + +int CSMWorld::CreatureAttackRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + // There are 3 attacks + return 3; +} + +CSMWorld::CreatureMiscRefIdAdapter::CreatureMiscRefIdAdapter() +{} + +CSMWorld::CreatureMiscRefIdAdapter::~CreatureMiscRefIdAdapter() +{} + +void CSMWorld::CreatureMiscRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + throw std::logic_error ("cannot add a row to a fixed table"); +} + +void CSMWorld::CreatureMiscRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + throw std::logic_error ("cannot remove a row to a fixed table"); +} + +void CSMWorld::CreatureMiscRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + throw std::logic_error ("table operation not supported"); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureMiscRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + throw std::logic_error ("table operation not supported"); +} + +QVariant CSMWorld::CreatureMiscRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + switch (subColIndex) + { + case 0: return creature.mData.mLevel; + case 1: return creature.mData.mHealth; + case 2: return creature.mData.mMana; + case 3: return creature.mData.mFatigue; + case 4: return creature.mData.mSoul; + case 5: return creature.mData.mCombat; + case 6: return creature.mData.mMagic; + case 7: return creature.mData.mStealth; + case 8: return creature.mData.mGold; + default: return QVariant(); // throw an exception here? + } +} + +void CSMWorld::CreatureMiscRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + switch(subColIndex) + { + case 0: creature.mData.mLevel = value.toInt(); break; + case 1: creature.mData.mHealth = value.toInt(); break; + case 2: creature.mData.mMana = value.toInt(); break; + case 3: creature.mData.mFatigue = value.toInt(); break; + case 4: creature.mData.mSoul = value.toInt(); break; + case 5: creature.mData.mCombat = value.toInt(); break; + case 6: creature.mData.mMagic = value.toInt(); break; + case 7: creature.mData.mStealth = value.toInt(); break; + case 8: creature.mData.mGold = value.toInt(); break; + default: return; // throw an exception here? + } + + record.setModified (creature); +} + +int CSMWorld::CreatureMiscRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 9; // Level, Health, Mana, Fatigue, Soul, Combat, Magic, Steath, Gold +} + +int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + return 1; // fixed at size 1 +} + CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns) : EnchantableColumns (columns) {} diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 9d1c1907b..0ce12a62f 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -659,12 +659,11 @@ namespace CSMWorld { std::map mFlags; const RefIdColumn *mType; - const RefIdColumn *mSoul; const RefIdColumn *mScale; const RefIdColumn *mOriginal; - const RefIdColumn *mCombat; - const RefIdColumn *mMagic; - const RefIdColumn *mStealth; + const RefIdColumn *mAttributes; + const RefIdColumn *mAttack; + const RefIdColumn *mMisc; CreatureColumns (const ActorColumns& actorColumns); }; @@ -908,6 +907,97 @@ namespace CSMWorld virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; }; + class CreatureAttributesRefIdAdapter : public NestedRefIdAdapterBase + { + public: + + CreatureAttributesRefIdAdapter (); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + + class CreatureAttackRefIdAdapter : public NestedRefIdAdapterBase + { + public: + + CreatureAttackRefIdAdapter (); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + + class CreatureMiscRefIdAdapter : public NestedRefIdAdapterBase + { + CreatureMiscRefIdAdapter (const CreatureMiscRefIdAdapter&); + CreatureMiscRefIdAdapter& operator= (const CreatureMiscRefIdAdapter&); + + public: + + CreatureMiscRefIdAdapter (); + virtual ~CreatureMiscRefIdAdapter(); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + template class EffectsListAdapter; diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index d29225d53..1a7db882b 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -307,21 +307,10 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mColumns.push_back (RefIdColumn (Columns::ColumnId_CreatureType, ColumnBase::Display_CreatureType)); creatureColumns.mType = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_SoulPoints, ColumnBase::Display_Integer)); - creatureColumns.mSoul = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_Scale, ColumnBase::Display_Float)); creatureColumns.mScale = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_OriginalCreature, ColumnBase::Display_Creature)); creatureColumns.mOriginal = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_CombatState, ColumnBase::Display_Integer)); - creatureColumns.mCombat = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_MagicState, ColumnBase::Display_Integer)); - creatureColumns.mMagic = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_StealthState, ColumnBase::Display_Integer)); - creatureColumns.mStealth = &mColumns.back(); static const struct { @@ -360,6 +349,59 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) creatureColumns.mFlags.insert (std::make_pair (respawn, ESM::Creature::Respawn)); + // Nested table + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureAttributes, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + creatureColumns.mAttributes = &mColumns.back(); + std::map creaAttrMap; + creaAttrMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttributesRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), creaAttrMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_AttributeValue, CSMWorld::ColumnBase::Display_Integer)); + + // Nested table + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureAttack, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + creatureColumns.mAttacks = &mColumns.back(); + std::map attackMap; + attackMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttackRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attackMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_CreatureAttack, CSMWorld::ColumnBase::Display_Integer, false, false)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MinAttack, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MaxAttack, CSMWorld::ColumnBase::Display_Integer)); + + // Nested list + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureMisc, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); + creatureColumns.mMisc = &mColumns.back(); + std::map creaMiscMap; + creaMiscMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureMiscRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), creaMiscMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_Integer, + ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SoulPoints, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_CombatState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MagicState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_StealthState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_OpenSound, ColumnBase::Display_Sound)); const RefIdColumn *openSound = &mColumns.back(); From 2f76a1510e3791c530cc5877d51afae74cfc94e1 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 12 Sep 2015 10:15:32 +1000 Subject: [PATCH 214/441] Remove AI flag from the UI and instead auto-detect whether to save AIDT records. Should resolve bug #2879. (cherry picked from commit 45aee1b5088cb110e06540356670b806887f7372) Conflicts: apps/opencs/model/world/refidadapterimp.hpp --- apps/opencs/model/world/columns.cpp | 1 - apps/opencs/model/world/columns.hpp | 2 +- apps/opencs/model/world/refidadapterimp.cpp | 6 ------ apps/opencs/model/world/refidadapterimp.hpp | 5 +---- apps/opencs/model/world/refidcollection.cpp | 2 -- components/esm/loadcrea.cpp | 7 ++++++- components/esm/loadnpc.cpp | 7 ++++++- 7 files changed, 14 insertions(+), 16 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 398fe2918..28e6fd050 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -71,7 +71,6 @@ namespace CSMWorld { ColumnId_Weight, "Weight" }, { ColumnId_EnchantmentPoints, "Enchantment Points" }, { ColumnId_Quality, "Quality" }, - { ColumnId_Ai, "AI" }, { ColumnId_AiHello, "AI Hello" }, { ColumnId_AiFlee, "AI Flee" }, { ColumnId_AiFight, "AI Fight" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index a8b8cf2d6..ea50822b9 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -65,7 +65,7 @@ namespace CSMWorld ColumnId_Weight = 50, ColumnId_EnchantmentPoints = 51, ColumnId_Quality = 52, - ColumnId_Ai = 53, + // unused ColumnId_AiHello = 54, ColumnId_AiFlee = 55, ColumnId_AiFight = 56, diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 3b0008d18..64c18a99b 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -1447,9 +1447,6 @@ QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - if (column==mActors.mHasAi) - return record.get().mHasAI!=0; - if (column==mActors.mHello) return record.get().mAiData.mHello; @@ -1631,9 +1628,6 @@ QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, c const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - if (column==mActors.mHasAi) - return record.get().mHasAI!=0; - if (column==mActors.mHello) return record.get().mAiData.mHello; diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 0ce12a62f..aac5ec781 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -485,7 +485,6 @@ namespace CSMWorld struct ActorColumns : public NameColumns { - const RefIdColumn *mHasAi; const RefIdColumn *mHello; const RefIdColumn *mFlee; const RefIdColumn *mFight; @@ -531,9 +530,7 @@ namespace CSMWorld data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); - if (column==mActors.mHasAi) - record2.mHasAI = value.toInt(); - else if (column==mActors.mHello) + if (column==mActors.mHello) record2.mAiData.mHello = value.toInt(); else if (column==mActors.mFlee) record2.mAiData.mFlee = value.toInt(); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 1a7db882b..588cdcd91 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -114,8 +114,6 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) ActorColumns actorsColumns (nameColumns); - mColumns.push_back (RefIdColumn (Columns::ColumnId_Ai, ColumnBase::Display_Boolean)); - actorsColumns.mHasAi = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiHello, ColumnBase::Display_Integer)); actorsColumns.mHello = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFlee, ColumnBase::Display_Integer)); diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 50c47349c..8c4f49e45 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -96,7 +96,12 @@ namespace ESM { mInventory.save(esm); mSpells.save(esm); - if (mHasAI) { + if (mAiData.mHello != 0 + || mAiData.mFight != 0 + || mAiData.mFlee != 0 + || mAiData.mAlarm != 0 + || mAiData.mServices != 0) + { esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); } mTransport.save(esm); diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 44d298785..67a437176 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -121,7 +121,12 @@ namespace ESM mInventory.save(esm); mSpells.save(esm); - if (mHasAI) { + if (mAiData.mHello != 0 + || mAiData.mFight != 0 + || mAiData.mFlee != 0 + || mAiData.mAlarm != 0 + || mAiData.mServices != 0) + { esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); } From 148ccb79e8fc117ca84f32bf894604285aae31e6 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 12 Sep 2015 10:17:14 +1000 Subject: [PATCH 215/441] Set default creature scale to 1. Partially resolves bug #2880. (no creature verifier yet) (cherry picked from commit 192f01e3ac5db4f0d1d9d78c2a419b274f307bb1) --- components/esm/loadcrea.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 8c4f49e45..fb235e6b3 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -120,7 +120,7 @@ namespace ESM { for (int i=0; i<6; ++i) mData.mAttack[i] = 0; mData.mGold = 0; mFlags = 0; - mScale = 0; + mScale = 1.f; mModel.clear(); mName.clear(); mScript.clear(); From ae1439f22380ecf6abc0e924da48bc42b4d22772 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 12 Sep 2015 11:18:07 +1000 Subject: [PATCH 216/441] Fix typo. (cherry picked from commit 1365b8edd1f0a20e97ccb9a30cd74a6dc97c44bd) --- apps/opencs/model/world/refidadapterimp.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index aac5ec781..3da273c89 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -659,7 +659,7 @@ namespace CSMWorld const RefIdColumn *mScale; const RefIdColumn *mOriginal; const RefIdColumn *mAttributes; - const RefIdColumn *mAttack; + const RefIdColumn *mAttacks; const RefIdColumn *mMisc; CreatureColumns (const ActorColumns& actorColumns); From a2294117cdcd209edf993898d1af6abf180753aa Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 15 Sep 2015 14:57:07 +0200 Subject: [PATCH 217/441] replaced context-sensitive implementation of allowing digits at the beginning of names with a more general implementation (Fixes #1730) (cherry picked from commit 4d94f38f4b7255fd1abbcdc8d2bd388e604bec04) --- components/compiler/fileparser.cpp | 2 -- components/compiler/scanner.cpp | 36 +++++++++++++++++------------- components/compiler/scanner.hpp | 3 --- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 423841ac3..e90e9a8f6 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -65,7 +65,6 @@ namespace Compiler if (mState==BeginState && keyword==Scanner::K_begin) { mState = NameState; - scanner.allowNameStartingwithDigit(); return true; } @@ -112,7 +111,6 @@ namespace Compiler scanner.scan (mScriptParser); mState = EndNameState; - scanner.allowNameStartingwithDigit(); return true; } diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 5af396d27..f25e69e39 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -47,9 +47,6 @@ namespace Compiler bool Scanner::scanToken (Parser& parser) { - bool allowDigit = mNameStartingWithDigit; - mNameStartingWithDigit = false; - switch (mPutback) { case Putback_Special: @@ -114,7 +111,6 @@ namespace Compiler else if (isWhitespace (c)) { mLoc.mLiteral.clear(); - mNameStartingWithDigit = allowDigit; return true; } else if (c==':') @@ -123,7 +119,7 @@ namespace Compiler mLoc.mLiteral.clear(); return true; } - else if (std::isalpha (c) || c=='_' || c=='"' || (allowDigit && std::isdigit (c))) + else if (std::isalpha (c) || c=='_' || c=='"') { bool cont = false; @@ -179,10 +175,18 @@ namespace Compiler { value += c; } - else if (std::isalpha (c) || c=='_') + else if (isStringCharacter (c)) + { error = true; - else if (c=='.' && !error) + value += c; + } + else if (c=='.') { + if (error) + { + putback (c); + break; + } return scanFloat (value, parser, cont); } else @@ -193,7 +197,15 @@ namespace Compiler } if (error) - return false; + { + /// workaround that allows names to begin with digits + /// \todo disable + TokenLoc loc (mLoc); + mLoc.mLiteral.clear(); + cont = parser.parseName (value, loc, *this); + return true; +// return false; + } TokenLoc loc (mLoc); mLoc.mLiteral.clear(); @@ -555,8 +567,7 @@ namespace Compiler Scanner::Scanner (ErrorHandler& errorHandler, std::istream& inputStream, const Extensions *extensions) : mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions), - mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0), - mNameStartingWithDigit (false) + mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0) { } @@ -608,9 +619,4 @@ namespace Compiler if (mExtensions) mExtensions->listKeywords (keywords); } - - void Scanner::allowNameStartingwithDigit() - { - mNameStartingWithDigit = true; - } } diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index ed01bbe44..847895978 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -124,9 +124,6 @@ namespace Compiler void listKeywords (std::vector& keywords); ///< Append all known keywords to \a kaywords. - - /// For the next token allow names to start with a digit. - void allowNameStartingwithDigit(); }; } From fcb9068c198b315567d9dc53b31ed8e1e6cf1cee Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 16 Sep 2015 20:45:37 +0200 Subject: [PATCH 218/441] Fix tab indentations in apps/ and components/ (cherry picked from commit a47617c21f11c33e10b8a635292c3b1a4d8d5769) Conflicts: apps/openmw/mwinput/inputmanagerimp.cpp apps/openmw/mwmechanics/aicombat.cpp apps/openmw/mwphysics/physicssystem.cpp --- apps/esmtool/record.cpp | 30 +++++++++---------- apps/esmtool/record.hpp | 2 +- .../model/world/infotableproxymodel.cpp | 2 +- apps/opencs/view/widget/droplineedit.cpp | 2 +- apps/openmw/android_commandLine.cpp | 26 ++++++++-------- apps/openmw/android_main.c | 20 ++++++------- apps/openmw/crashcatcher.cpp | 4 +-- apps/openmw/mwgui/debugwindow.cpp | 8 ++--- apps/openmw/mwgui/tooltips.hpp | 2 +- apps/openmw/mwgui/widgets.cpp | 4 +-- apps/openmw/mwinput/inputmanagerimp.cpp | 14 ++++----- apps/openmw/mwmechanics/aicombat.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- components/compiler/extensions0.hpp | 2 +- components/esm/loadmgef.hpp | 4 +-- components/files/configurationmanager.hpp | 10 +++---- extern/sdl4ogre/sdlinputwrapper.hpp | 4 +-- 17 files changed, 69 insertions(+), 69 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 76c14e728..5fe6471b0 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -493,14 +493,14 @@ void Record::print() std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; if (mPrintPlain) { - std::cout << " Text:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mText << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Text:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mText << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Text: [skipped]" << std::endl; + std::cout << " Text: [skipped]" << std::endl; } } @@ -799,14 +799,14 @@ void Record::print() { if (mPrintPlain) { - std::cout << " Result Script:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mResultScript << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Result Script:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mResultScript << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Result Script: [skipped]" << std::endl; + std::cout << " Result Script: [skipped]" << std::endl; } } } @@ -1201,14 +1201,14 @@ void Record::print() if (mPrintPlain) { - std::cout << " Script:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mScriptText << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Script:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mScriptText << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Script: [skipped]" << std::endl; + std::cout << " Script: [skipped]" << std::endl; } } diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index c1b90ac2b..5e03c64db 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -53,7 +53,7 @@ namespace EsmTool } void setPrintPlain(bool plain) { - mPrintPlain = plain; + mPrintPlain = plain; } virtual void load(ESM::ESMReader &esm) = 0; diff --git a/apps/opencs/model/world/infotableproxymodel.cpp b/apps/opencs/model/world/infotableproxymodel.cpp index f55f775ab..4f0b2e752 100644 --- a/apps/opencs/model/world/infotableproxymodel.cpp +++ b/apps/opencs/model/world/infotableproxymodel.cpp @@ -16,7 +16,7 @@ namespace CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type type, QObject *parent) : IdTableProxyModel(parent), mType(type), - mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic : + mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic : Columns::ColumnId_Journal), mInfoColumnIndex(-1), mLastAddedSourceRow(-1) diff --git a/apps/opencs/view/widget/droplineedit.cpp b/apps/opencs/view/widget/droplineedit.cpp index 1df598cb8..2ca306461 100644 --- a/apps/opencs/view/widget/droplineedit.cpp +++ b/apps/opencs/view/widget/droplineedit.cpp @@ -35,7 +35,7 @@ void CSVWidget::DropLineEdit::dropEvent(QDropEvent *event) if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) { CSMWorld::UniversalId id = CSVWorld::DragDropUtils::getAcceptedData(*event, mDropType); - setText(QString::fromUtf8(id.getId().c_str())); + setText(QString::fromUtf8(id.getId().c_str())); emit tableMimeDataDropped(id, CSVWorld::DragDropUtils::getTableMimeData(*event)->getDocumentPtr()); } } diff --git a/apps/openmw/android_commandLine.cpp b/apps/openmw/android_commandLine.cpp index ebfff28ca..7e7f368e0 100644 --- a/apps/openmw/android_commandLine.cpp +++ b/apps/openmw/android_commandLine.cpp @@ -7,21 +7,21 @@ int argcData; extern "C" void releaseArgv(); void releaseArgv() { - delete[] argvData; + delete[] argvData; } JNIEXPORT void JNICALL Java_ui_activity_GameActivity_commandLine(JNIEnv *env, - jobject obj, jint argc, jobjectArray stringArray) { - jboolean iscopy; - argcData = (int) argc; - argvData = new const char *[argcData + 1]; - argvData[0] = "openmw"; - for (int i = 1; i < argcData + 1; i++) { - jstring string = (jstring) (env)->GetObjectArrayElement(stringArray, - i - 1); - argvData[i] = (env)->GetStringUTFChars(string, &iscopy); - (env)->DeleteLocalRef(string); - } - (env)->DeleteLocalRef(stringArray); + jobject obj, jint argc, jobjectArray stringArray) { + jboolean iscopy; + argcData = (int) argc; + argvData = new const char *[argcData + 1]; + argvData[0] = "openmw"; + for (int i = 1; i < argcData + 1; i++) { + jstring string = (jstring) (env)->GetObjectArrayElement(stringArray, + i - 1); + argvData[i] = (env)->GetStringUTFChars(string, &iscopy); + (env)->DeleteLocalRef(string); + } + (env)->DeleteLocalRef(stringArray); } diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c index 1b2839519..47b77a8b3 100644 --- a/apps/openmw/android_main.c +++ b/apps/openmw/android_main.c @@ -16,22 +16,22 @@ extern const char **argvData; void releaseArgv(); int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, - jobject obj) { + jobject obj) { - SDL_Android_Init(env, cls); + SDL_Android_Init(env, cls); - SDL_SetMainReady(); + SDL_SetMainReady(); - /* Run the application code! */ + /* Run the application code! */ - int status; + int status; - status = main(argcData+1, argvData); - releaseArgv(); - /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ - /* exit(status); */ + status = main(argcData+1, argvData); + releaseArgv(); + /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ + /* exit(status); */ - return status; + return status; } #endif /* __ANDROID__ */ diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 8f25d041c..373d78746 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -37,8 +37,8 @@ static const char pipe_err[] = "!!! Failed to create pipe\n"; static const char fork_err[] = "!!! Failed to fork debug process\n"; static const char exec_err[] = "!!! Failed to exec debug process\n"; -#ifndef PATH_MAX /* Not all platforms (GNU Hurd) have this. */ -# define PATH_MAX 256 +#ifndef PATH_MAX /* Not all platforms (GNU Hurd) have this. */ +# define PATH_MAX 256 #endif static char argv0[PATH_MAX]; diff --git a/apps/openmw/mwgui/debugwindow.cpp b/apps/openmw/mwgui/debugwindow.cpp index a6dab66c1..37ea3470d 100644 --- a/apps/openmw/mwgui/debugwindow.cpp +++ b/apps/openmw/mwgui/debugwindow.cpp @@ -18,9 +18,9 @@ namespace float accumulated_time=0,parent_time = pit->Is_Root() ? CProfileManager::Get_Time_Since_Reset() : pit->Get_Current_Parent_Total_Time(); int i,j; int frames_since_reset = CProfileManager::Get_Frame_Count_Since_Reset(); - for (i=0;iGet_Current_Parent_Name())+" (total running time: "+MyGUI::utility::toString(parent_time,3)+" ms) ---\n"; os << s; @@ -35,7 +35,7 @@ namespace accumulated_time += current_total_time; float fraction = parent_time > SIMD_EPSILON ? (current_total_time / parent_time) * 100 : 0.f; - for (j=0;jGet_Current_Name()+" ("+MyGUI::utility::toString(fraction,2)+" %) :: "+MyGUI::utility::toString(ms,3)+" ms / frame ("+MyGUI::utility::toString(pit->Get_Current_Total_Calls())+" calls)\n"; os << s; @@ -47,7 +47,7 @@ namespace { os << "what's wrong\n"; } - for (i=0;i SIMD_EPSILON ? ((parent_time - accumulated_time) / parent_time) * 100 : 0.f; s = "Unaccounted: ("+MyGUI::utility::toString(unaccounted,3)+" %) :: "+MyGUI::utility::toString(parent_time - accumulated_time,3)+" ms\n"; os << s; diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index cf5bb14a2..84bd15e12 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -106,7 +106,7 @@ namespace MWGui static std::string sSchoolNames[6]; - int mHorizontalScrollIndex; + int mHorizontalScrollIndex; float mDelay; diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 718624a16..f373ce2c4 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -607,7 +607,7 @@ namespace MWGui controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); controller->setEnabled(mEnableRepeat); controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); - MyGUI::ControllerManager::getInstance().addItem(this, controller); + MyGUI::ControllerManager::getInstance().addItem(this, controller); } void MWScrollBar::onDecreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) @@ -623,7 +623,7 @@ namespace MWGui controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); controller->setEnabled(mEnableRepeat); controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); - MyGUI::ControllerManager::getInstance().addItem(this, controller); + MyGUI::ControllerManager::getInstance().addItem(this, controller); } void MWScrollBar::onIncreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 2e82faa6d..795d81ff9 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -159,9 +159,9 @@ namespace MWInput mControlSwitch["playerviewswitch"] = true; mControlSwitch["vanitymode"] = true; - /* Joystick Init */ + /* Joystick Init */ - //Load controller mappings + // Load controller mappings #if SDL_VERSION_ATLEAST(2,0,2) if(controllerBindingsFile!="") { @@ -169,10 +169,10 @@ namespace MWInput } #endif - //Open all presently connected sticks - int numSticks = SDL_NumJoysticks(); - for(int i = 0; i < numSticks; i++) - { + // Open all presently connected sticks + int numSticks = SDL_NumJoysticks(); + for(int i = 0; i < numSticks; i++) + { if(SDL_IsGameController(i)) { SDL_ControllerDeviceEvent evt; @@ -183,7 +183,7 @@ namespace MWInput { //ICS_LOG(std::string("Unusable controller plugged in: ")+SDL_JoystickNameForIndex(i)); } - } + } } void InputManager::clear() diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8f43c6280..5f0eff4e9 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -65,7 +65,7 @@ namespace Ogre::Vector3 dir = to - from; dir.z = 0; dir.normalise(); - float verticalOffset = 200; // instead of '200' here we want the height of the actor + float verticalOffset = 200; // instead of '200' here we want the height of the actor Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset; // cast up-down ray and find height in world space of hit diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index a1aa0166d..b6d5c8e93 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -287,7 +287,7 @@ namespace MWWorld if (mPlayer) { - mPlayer->clear(); + mPlayer->clear(); mPlayer->setCell(0); mPlayer->getPlayer().getRefData() = RefData(); mPlayer->set(mStore.get().find ("player")); diff --git a/components/compiler/extensions0.hpp b/components/compiler/extensions0.hpp index 83f3a44fa..e9ce6a6d6 100644 --- a/components/compiler/extensions0.hpp +++ b/components/compiler/extensions0.hpp @@ -44,7 +44,7 @@ namespace Compiler namespace Gui { - void registerExtensions (Extensions& extensions); + void registerExtensions (Extensions& extensions); } namespace Misc diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index 32b8a85a6..eeb4268c2 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -31,9 +31,9 @@ struct MagicEffect CastTouch = 0x80, // Allows range - cast on touch. CastTarget = 0x100, // Allows range - cast on target. UncappedDamage = 0x1000, // Negates multiple cap behaviours. Allows an effect to reduce an attribute below zero; removes the normal minimum effect duration of 1 second. - NonRecastable = 0x4000, // Does not land if parent spell is already affecting target. Shows "you cannot re-cast" message for self target. + NonRecastable = 0x4000, // Does not land if parent spell is already affecting target. Shows "you cannot re-cast" message for self target. Unreflectable = 0x10000, // Cannot be reflected, the effect always lands normally. - CasterLinked = 0x20000, // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells. + CasterLinked = 0x20000, // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells. // Originally modifiable flags AllowSpellmaking = 0x200, // Can be used for spellmaking diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 63ae3c6a4..c7c738b19 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -56,11 +56,11 @@ struct ConfigurationManager typedef Files::FixedPath<> FixedPathType; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; - #if defined HAVE_UNORDERED_MAP - typedef std::unordered_map TokensMappingContainer; - #else - typedef std::tr1::unordered_map TokensMappingContainer; - #endif + #if defined HAVE_UNORDERED_MAP + typedef std::unordered_map TokensMappingContainer; + #else + typedef std::tr1::unordered_map TokensMappingContainer; + #endif void loadConfig(const boost::filesystem::path& path, boost::program_options::variables_map& variables, diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index a7023207c..4b57a1c95 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -27,8 +27,8 @@ namespace SFO void setControllerEventCallback(ControllerListener* listen) { mConListener = listen; } void capture(bool windowEventsOnly); - bool isModifierHeld(SDL_Keymod mod); - bool isKeyDown(SDL_Scancode key); + bool isModifierHeld(SDL_Keymod mod); + bool isKeyDown(SDL_Scancode key); void setMouseVisible (bool visible); void setMouseRelative(bool relative); From 9179782002492fe5637a67cfa3527fac59024717 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 16 Sep 2015 20:50:57 +0200 Subject: [PATCH 219/441] Add travis-ci script to detect tab characters in the code (cherry picked from commit ab97a90c760fb3a558f25bd49527bf69fbba8d64) --- .travis.yml | 2 +- CI/check_tabs.sh | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100755 CI/check_tabs.sh diff --git a/.travis.yml b/.travis.yml index 6b03be114..bd4aefbdb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,8 +40,8 @@ script: - cd ./build - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j2; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi -after_script: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi + - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi notifications: recipients: - corrmage+travis-ci@gmail.com diff --git a/CI/check_tabs.sh b/CI/check_tabs.sh new file mode 100755 index 000000000..91f81e2a1 --- /dev/null +++ b/CI/check_tabs.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +OUTPUT=$(grep -nRP '\t' --include=\*.{cpp,hpp,c,h} apps components) + +if [[ $OUTPUT ]] ; then + echo "Error: Tab characters found!" + echo $OUTPUT + exit 1 +fi From 46c6092e99642efed263812766c61ec697b0dba8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 17 Sep 2015 09:39:42 +0200 Subject: [PATCH 220/441] updated credits file (cherry picked from commit 56163dcb2a33fdf67bd78dc63a6134c4f408505a) --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 8ad420baf..a5d4d4ea8 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -110,6 +110,7 @@ Programmers viadanna Vincent Heuken vocollapse + zelurker Manual ------ From 6f294d0d25b99d81a95d1b6123645047aa4ded48 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 17 Sep 2015 11:27:55 +0200 Subject: [PATCH 221/441] removed some left-over signal slot stuff that shouldn't have been there in the first place (cherry picked from commit b3b4fb3efbaf84a31b9a62203f9767b08328b671) --- apps/opencs/view/world/scenesubview.cpp | 7 ------- apps/opencs/view/world/scenesubview.hpp | 8 -------- 2 files changed, 15 deletions(-) diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 2ca2548c0..b698d9034 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -247,8 +247,6 @@ void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceW mToolbar = toolbar; connect (mScene, SIGNAL (focusToolbarRequest()), mToolbar, SLOT (setFocus())); - connect (this, SIGNAL (updateSceneUserSetting(const QString &, const QStringList &)), - mScene, SLOT (updateUserSetting(const QString &, const QStringList &))); connect (mToolbar, SIGNAL (focusSceneRequest()), mScene, SLOT (setFocus())); mLayout->addWidget (mToolbar, 0); @@ -257,8 +255,3 @@ void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceW mScene->selectDefaultNavigationMode(); setFocusProxy (mScene); } - -void CSVWorld::SceneSubView::updateUserSetting (const QString &key, const QStringList &list) -{ - emit updateSceneUserSetting(key, list); -} diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index fc45347d0..65aaca999 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -83,14 +83,6 @@ namespace CSVWorld void cellSelectionChanged (const CSMWorld::UniversalId& id); void handleDrop(const std::vector& data); - - public slots: - - void updateUserSetting (const QString &, const QStringList &); - - signals: - - void updateSceneUserSetting (const QString &, const QStringList &); }; } From 14708e181c82c1abe4625317f2e50b9ca1f5890b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 17 Sep 2015 11:31:25 +0200 Subject: [PATCH 222/441] more cleanup (cherry picked from commit 659b87b25faec71089ae5f8b96b4283d58abaab7) Conflicts: apps/opencs/view/doc/view.hpp --- apps/opencs/view/doc/view.hpp | 3 --- apps/opencs/view/world/scenesubview.cpp | 5 ----- apps/opencs/view/world/scenesubview.hpp | 2 -- 3 files changed, 10 deletions(-) diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 04d31dea5..f83e49627 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -118,9 +118,6 @@ namespace CSVDoc Operations *getOperations() const; - /// Function called by view manager when user preferences are updated - void updateEditorSetting (const QString &, const QString &); - protected: virtual void moveEvent(QMoveEvent * event); diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index b698d9034..c2f3442f8 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -132,11 +132,6 @@ void CSVWorld::SceneSubView::setEditLock (bool locked) } -void CSVWorld::SceneSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) -{ - -} - void CSVWorld::SceneSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index 65aaca999..a34d71901 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -52,8 +52,6 @@ namespace CSVWorld virtual void setEditLock (bool locked); - virtual void updateEditorSetting (const QString& key, const QString& value); - virtual void setStatusBar (bool show); virtual void useHint (const std::string& hint); From 69e63285078a5bc4c202a4ad16b795a936b48f5e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 22 Sep 2015 15:36:00 +0200 Subject: [PATCH 223/441] fixed local variable caching issue in automatic error checking (Fixes #2927) (cherry picked from commit 7bef97bf3331dbdf9544c2caad17ecf68b3be96b) --- apps/opencs/model/world/scriptcontext.cpp | 17 +++++++++++-- apps/opencs/model/world/scriptcontext.hpp | 3 +++ apps/opencs/view/world/scripterrortable.cpp | 5 ++++ apps/opencs/view/world/scripterrortable.hpp | 5 ++++ apps/opencs/view/world/scriptsubview.cpp | 27 ++++++++++++++++++++- apps/opencs/view/world/scriptsubview.hpp | 1 + 6 files changed, 55 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp index bcbca4b28..f644ad37a 100644 --- a/apps/opencs/model/world/scriptcontext.cpp +++ b/apps/opencs/model/world/scriptcontext.cpp @@ -39,8 +39,6 @@ char CSMWorld::ScriptContext::getGlobalType (const std::string& name) const std::pair CSMWorld::ScriptContext::getMemberType (const std::string& name, const std::string& id) const { - /// \todo invalidate locals cache on change to scripts - std::string id2 = Misc::StringUtils::lowerCase (id); int index = mData.getScripts().searchId (id2); @@ -120,3 +118,18 @@ void CSMWorld::ScriptContext::clear() mIdsUpdated = false; mLocals.clear(); } + +bool CSMWorld::ScriptContext::clearLocals (const std::string& script) +{ + std::map::iterator iter = + mLocals.find (Misc::StringUtils::lowerCase (script)); + + if (iter!=mLocals.end()) + { + mLocals.erase (iter); + mIdsUpdated = false; + return true; + } + + return false; +} diff --git a/apps/opencs/model/world/scriptcontext.hpp b/apps/opencs/model/world/scriptcontext.hpp index 29ee42645..2cd59f070 100644 --- a/apps/opencs/model/world/scriptcontext.hpp +++ b/apps/opencs/model/world/scriptcontext.hpp @@ -46,6 +46,9 @@ namespace CSMWorld void clear(); ///< Remove all cached data. + + /// \return Were there any locals that needed clearing? + bool clearLocals (const std::string& script); }; } diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index b44e1c0bd..a9e315c73 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -131,6 +131,11 @@ void CSVWorld::ScriptErrorTable::clear() setRowCount (0); } +bool CSVWorld::ScriptErrorTable::clearLocals (const std::string& script) +{ + return mContext.clearLocals (script); +} + void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) { if (item (row, 1)) diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp index 98db425cf..33af7c864 100644 --- a/apps/opencs/view/world/scripterrortable.hpp +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -44,6 +44,11 @@ namespace CSVWorld void clear(); + /// Clear local variable cache for \a script. + /// + /// \return Were there any locals that needed clearing? + bool clearLocals (const std::string& script); + private slots: void cellClicked (int row, int column); diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index d405d1765..d99f789b3 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -89,6 +89,7 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); mColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_ScriptText); + mIdColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); mStateColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString(); @@ -241,6 +242,15 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo ScriptEdit::ChangeLock lock (*mEditor); + bool updateRequired = false; + + for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + { + std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals (id)) + updateRequired = true; + } + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) @@ -256,13 +266,28 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo mEditor->setPlainText (source); mEditor->setTextCursor (cursor); - recompile(); + updateRequired = true; } } + + if (updateRequired) + recompile(); } void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end) { + bool updateRequired = false; + + for (int i=start; i<=end; ++i) + { + std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals (id)) + updateRequired = true; + } + + if (updateRequired) + recompile(); + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); if (!parent.isValid() && index.row()>=start && index.row()<=end) diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 6125dd259..907dc7958 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -38,6 +38,7 @@ namespace CSVWorld CSMDoc::Document& mDocument; CSMWorld::IdTable *mModel; int mColumn; + int mIdColumn; int mStateColumn; TableBottomBox *mBottom; RecordButtonBar *mButtons; From 890405bdb154f689e61d490e654fcf8083ebf992 Mon Sep 17 00:00:00 2001 From: Emmanuel Anne Date: Fri, 25 Sep 2015 14:18:08 +0200 Subject: [PATCH 224/441] scripts: recognize '+' also as a unary operator it fixes the armor sorter in "Blades safe house.esp" (cherry picked from commit dace7ab7060c47155d70170a091b465c8b3b2688) --- components/compiler/exprparser.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index b588b6196..53b24eab6 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -650,8 +650,13 @@ namespace Compiler mOperators.push_back ('m'); mTokenLoc = loc; return true; + } else if (code ==Scanner::S_plus && mNextOperand) { + // Also unary, but +, just ignore it + mTokenLoc = loc; + return true; } + if (code==Scanner::S_open) { if (mNextOperand) From c5235ea7942c1466a951364f7009d5d00bc44600 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 26 Sep 2015 11:30:35 +0200 Subject: [PATCH 225/441] removed a redundant else and made unary + work also in the console (cherry picked from commit ae54f34f25ce08a2abc5ee9a162292940d256c84) --- components/compiler/exprparser.cpp | 6 ++++-- components/compiler/lineparser.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 53b24eab6..c0375b436 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -650,13 +650,15 @@ namespace Compiler mOperators.push_back ('m'); mTokenLoc = loc; return true; - } else if (code ==Scanner::S_plus && mNextOperand) { + } + + if (code ==Scanner::S_plus && mNextOperand) + { // Also unary, but +, just ignore it mTokenLoc = loc; return true; } - if (code==Scanner::S_open) { if (mNextOperand) diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 032af7d65..c1622c3e0 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -555,7 +555,7 @@ namespace Compiler } if (mAllowExpression && mState==BeginState && - (code==Scanner::S_open || code==Scanner::S_minus)) + (code==Scanner::S_open || code==Scanner::S_minus || code==Scanner::S_plus)) { scanner.putbackSpecial (code, loc); parseExpression (scanner, loc); From 46775608595f24ed140e08f0944d5f264ed889ae Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 26 Sep 2015 11:34:46 +0200 Subject: [PATCH 226/441] adjusted a workaround for names starting with digits that interfered with some numerical expressions written without spaces (cherry picked from commit 435e52306a0b1a7ef4cf7059bbc388b3262d2631) --- components/compiler/scanner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index f25e69e39..3c5bb7747 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -175,7 +175,7 @@ namespace Compiler { value += c; } - else if (isStringCharacter (c)) + else if (c!='-' && isStringCharacter (c)) { error = true; value += c; From cb6198a843d01a1e45210bc39b98af9ca34a2c9f Mon Sep 17 00:00:00 2001 From: "artemutin@yandex.ru" Date: Tue, 29 Sep 2015 10:46:03 +1000 Subject: [PATCH 227/441] Added StartScriptCreator class with redefined getErrors method. (cherry picked from commit 3f27c856300671083123d3027cf7b6299bc01438) --- apps/opencs/view/world/startscriptcreator.cpp | 26 +++++++++++++++++++ apps/opencs/view/world/startscriptcreator.hpp | 25 ++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 apps/opencs/view/world/startscriptcreator.cpp create mode 100644 apps/opencs/view/world/startscriptcreator.hpp diff --git a/apps/opencs/view/world/startscriptcreator.cpp b/apps/opencs/view/world/startscriptcreator.cpp new file mode 100644 index 000000000..5812cd931 --- /dev/null +++ b/apps/opencs/view/world/startscriptcreator.cpp @@ -0,0 +1,26 @@ +#include "startscriptcreator.hpp" + +StartScriptCreator::StartScriptCreator() +{ + +} + + +CSVWORLD::StartScriptCreator::StartScriptCreator(CSMWorld::Data &data, QUndoStack &undoStack, const CSMWorld::UniversalId &id, bool relaxedIdRules): + GenericCreator (data, undoStack, id, true) +{} + +std::string CSVWORLD::StartScriptCreator::getErrors() const +{ + std::string errors; + + errors = getIdValidatorResult(); + if (errors.length() > 0) + return errors; + else if (getData().getScripts().searchId(getId()) == -1) + errors = "Script ID not found"; + else if (getData().getStartScripts().searchId(getId()) > -1 ) + errors = "Script with this ID already registered as Start Script"; + + return errors; +} diff --git a/apps/opencs/view/world/startscriptcreator.hpp b/apps/opencs/view/world/startscriptcreator.hpp new file mode 100644 index 000000000..4c91ad96a --- /dev/null +++ b/apps/opencs/view/world/startscriptcreator.hpp @@ -0,0 +1,25 @@ +#ifndef STARTSCRIPTCREATOR_HPP +#define STARTSCRIPTCREATOR_HPP + +#include "genericcreator.hpp" + +namespace CSVWORLD { + + class StartScriptCreator : public GenericCreator + { + Q_OBJECT + + public: + StartScriptCreator(CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id, bool relaxedIdRules = false); + + virtual std::string getErrors() const; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. + }; + +} + + + +#endif // STARTSCRIPTCREATOR_HPP From b0018aa7913d162b5af018f04799baf04dcbe746 Mon Sep 17 00:00:00 2001 From: "artemutin@yandex.ru" Date: Tue, 29 Sep 2015 22:43:20 +1000 Subject: [PATCH 228/441] Fix namespace, add file to CMakeLists.txt (cherry picked from commit e672880f644066716452d363b61eff1c56010b0f) --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/startscriptcreator.cpp | 10 ++-------- apps/opencs/view/world/startscriptcreator.hpp | 4 ++-- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 3b756495d..cd75db705 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -67,7 +67,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator - cellcreator referenceablecreator referencecreator scenesubview + cellcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator ) diff --git a/apps/opencs/view/world/startscriptcreator.cpp b/apps/opencs/view/world/startscriptcreator.cpp index 5812cd931..69b1b3ff1 100644 --- a/apps/opencs/view/world/startscriptcreator.cpp +++ b/apps/opencs/view/world/startscriptcreator.cpp @@ -1,16 +1,10 @@ #include "startscriptcreator.hpp" -StartScriptCreator::StartScriptCreator() -{ - -} - - -CSVWORLD::StartScriptCreator::StartScriptCreator(CSMWorld::Data &data, QUndoStack &undoStack, const CSMWorld::UniversalId &id, bool relaxedIdRules): +CSVWorld::StartScriptCreator::StartScriptCreator(CSMWorld::Data &data, QUndoStack &undoStack, const CSMWorld::UniversalId &id, bool relaxedIdRules): GenericCreator (data, undoStack, id, true) {} -std::string CSVWORLD::StartScriptCreator::getErrors() const +std::string CSVWorld::StartScriptCreator::getErrors() const { std::string errors; diff --git a/apps/opencs/view/world/startscriptcreator.hpp b/apps/opencs/view/world/startscriptcreator.hpp index 4c91ad96a..07fe8ff3d 100644 --- a/apps/opencs/view/world/startscriptcreator.hpp +++ b/apps/opencs/view/world/startscriptcreator.hpp @@ -3,7 +3,7 @@ #include "genericcreator.hpp" -namespace CSVWORLD { +namespace CSVWorld { class StartScriptCreator : public GenericCreator { @@ -16,7 +16,7 @@ namespace CSVWORLD { virtual std::string getErrors() const; ///< Return formatted error descriptions for the current state of the creator. if an empty /// string is returned, there is no error. - }; + }; } From 7f46bc155a9899f4c784003f4599c2d5238b1104 Mon Sep 17 00:00:00 2001 From: "artemutin@yandex.ru" Date: Tue, 29 Sep 2015 22:44:25 +1000 Subject: [PATCH 229/441] add getIdValidatorResult method to GenericCreator, for use in subclass StartScriptCreator (cherry picked from commit 903cd3322b2a24e4d0a757ede8e429583f34f2b4) --- apps/opencs/view/world/genericcreator.cpp | 10 ++++++++++ apps/opencs/view/world/genericcreator.hpp | 2 ++ 2 files changed, 12 insertions(+) diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index 8ed52bf80..df7739941 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -47,6 +47,16 @@ std::string CSVWorld::GenericCreator::getId() const return mId->text().toUtf8().constData(); } +std::string CSVWorld::GenericCreator::getIdValidatorResult() const +{ + std::string errors; + + if (!mId->hasAcceptableInput()) + errors = mValidator->getError(); + + return errors; +} + void CSVWorld::GenericCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const {} void CSVWorld::GenericCreator::pushCommand (std::auto_ptr command, diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 471d0622e..f63c45109 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -60,6 +60,8 @@ namespace CSVWorld virtual std::string getId() const; + virtual std::string getIdValidatorResult() const; + /// Allow subclasses to add additional data to \a command. virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; From c34d8c2a0dfaba38b1a340c146ecab2f2cc582de Mon Sep 17 00:00:00 2001 From: "artemutin@yandex.ru" Date: Tue, 29 Sep 2015 23:52:35 +1000 Subject: [PATCH 230/441] Add StartScriptCreator to a factory manager. (cherry picked from commit ecce3a197550cd8b689e7d2bf697461325a7c6b9) Conflicts: apps/opencs/view/world/subviews.cpp --- apps/opencs/view/world/subviews.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 47a109335..cffe283d4 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -10,6 +10,7 @@ #include "cellcreator.hpp" #include "referenceablecreator.hpp" #include "referencecreator.hpp" +#include "startscriptcreator.hpp" #include "scenesubview.hpp" #include "dialoguecreator.hpp" #include "infocreator.hpp" @@ -53,6 +54,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (sTableTypes[i], new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_StartScripts, + new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_Cells, new CSVDoc::SubViewFactoryWithCreator >); @@ -125,7 +129,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_BodyPart, CSMWorld::UniversalId::Type_SoundGen, CSMWorld::UniversalId::Type_Pathgrid, - CSMWorld::UniversalId::Type_StartScript, CSMWorld::UniversalId::Type_None // end marker }; @@ -135,6 +138,10 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_StartScript, + new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_Skill, new CSVDoc::SubViewFactoryWithCreator (false)); From 8fffbb3f9b153220fc2ce7751dccf0a1df918f02 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 30 Sep 2015 09:44:05 +0200 Subject: [PATCH 231/441] updated credits file (cherry picked from commit 64902bf22112d0b44464f7657b12db86627cdd38) --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index a5d4d4ea8..565d5a703 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -19,6 +19,7 @@ Programmers Alexander Nadeau (wareya) Alexander Olofsson (Ace) Artem Kotsynyak (greye) + artemutin Arthur Moore (EmperorArthur) athile Bret Curtis (psi29a) From 9cc046ce4e69d3a5d36d40217850d0996aa19eb4 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 8 Oct 2015 12:35:09 +1100 Subject: [PATCH 232/441] Further rationalise the use of ColumnIds - Address Zini's review comments as per https://github.com/OpenMW/openmw/pull/755 (cherry picked from commit 998348e606182eb171dd82b99b647e3dff798b26) --- apps/opencs/model/world/columnbase.cpp | 3 +-- apps/opencs/model/world/columnbase.hpp | 3 +-- apps/opencs/model/world/columns.cpp | 7 +++---- apps/opencs/model/world/columns.hpp | 6 +++--- apps/opencs/model/world/data.cpp | 10 +++++----- apps/opencs/model/world/refidcollection.cpp | 4 ++-- apps/opencs/view/doc/viewmanager.cpp | 2 +- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 2143ec730..39232d442 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -77,7 +77,7 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_Video, Display_Id, - Display_SkillImpact, + Display_SkillId, Display_EffectRange, Display_EffectId, Display_PartRefType, @@ -85,7 +85,6 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, - Display_RaceSkill, Display_None }; diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 400e31333..e2871d4d8 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -113,7 +113,7 @@ namespace CSMWorld Display_SoundGeneratorType, Display_School, Display_Id, - Display_SkillImpact, + Display_SkillId, Display_EffectRange, Display_EffectId, Display_PartRefType, @@ -121,7 +121,6 @@ namespace CSMWorld Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, - Display_RaceSkill, Display_String32, Display_LongString256, diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 28e6fd050..6adc66951 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -268,7 +268,7 @@ namespace CSMWorld { ColumnId_LevelledItemChanceNone, "Chance None" }, { ColumnId_PowerList, "Powers" }, - { ColumnId_SkillImpact, "Skill" }, + { ColumnId_Skill, "Skill" }, { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, @@ -294,8 +294,7 @@ namespace CSMWorld { ColumnId_NpcPersistence, "Persistent" }, { ColumnId_RaceAttributes, "Race Attributes" }, - { ColumnId_RaceMaleValue, "Male Attrib" }, - { ColumnId_RaceFemaleValue, "Female Attrib" }, + { ColumnId_Male, "Male" }, { ColumnId_RaceSkillBonus, "Skill Bonus" }, { ColumnId_RaceBonus, "Bonus" }, @@ -577,7 +576,7 @@ namespace case CSMWorld::Columns::ColumnId_MeshType: return sMeshTypes; case CSMWorld::Columns::ColumnId_SoundGeneratorType: return sSoundGeneratorType; case CSMWorld::Columns::ColumnId_School: return sSchools; - case CSMWorld::Columns::ColumnId_SkillImpact: return sSkills; + case CSMWorld::Columns::ColumnId_Skill: return sSkills; case CSMWorld::Columns::ColumnId_EffectRange: return sEffectRange; case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index ea50822b9..1992f225a 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -261,7 +261,7 @@ namespace CSMWorld ColumnId_LevelledItemChanceNone = 238, ColumnId_PowerList = 239, - ColumnId_SkillImpact = 240, // impact from magic effects + ColumnId_Skill = 240, ColumnId_InfoList = 241, ColumnId_InfoCondition = 242, @@ -288,8 +288,8 @@ namespace CSMWorld ColumnId_NpcPersistence = 261, ColumnId_RaceAttributes = 262, - ColumnId_RaceMaleValue = 263, - ColumnId_RaceFemaleValue = 264, + ColumnId_Male = 263, + // unused ColumnId_RaceSkillBonus = 265, // unused ColumnId_RaceBonus = 267, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index fee1cf2b4..10297df5b 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -212,15 +212,15 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute, ColumnBase::Flag_Dialogue, false)); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceMaleValue, ColumnBase::Display_Integer)); + new NestedChildColumn (Columns::ColumnId_Male, ColumnBase::Display_Integer)); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceFemaleValue, ColumnBase::Display_Integer)); + new NestedChildColumn (Columns::ColumnId_Female, ColumnBase::Display_Integer)); // Race skill bonus mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus)); index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_RaceBonus, ColumnBase::Display_Integer)); @@ -282,7 +282,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); mSpells.getNestableColumn(index)->addColumn( @@ -398,7 +398,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); mEnchantments.getNestableColumn(index)->addColumn( diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 588cdcd91..b775d8156 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -84,7 +84,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); mColumns.back().addColumn( @@ -501,7 +501,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_SkillImpact, CSMWorld::ColumnBase::Display_SkillImpact, false, false)); + new RefIdColumn (Columns::ColumnId_Skill, CSMWorld::ColumnBase::Display_SkillId, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_Integer)); diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 4590a2dd0..51755c7e2 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -97,7 +97,7 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_Gender, CSMWorld::Columns::ColumnId_Gender, true }, { CSMWorld::ColumnBase::Display_SoundGeneratorType, CSMWorld::Columns::ColumnId_SoundGeneratorType, false }, { CSMWorld::ColumnBase::Display_School, CSMWorld::Columns::ColumnId_School, false }, - { CSMWorld::ColumnBase::Display_SkillImpact, CSMWorld::Columns::ColumnId_SkillImpact, true }, + { CSMWorld::ColumnBase::Display_SkillId, CSMWorld::Columns::ColumnId_Skill, true }, { CSMWorld::ColumnBase::Display_EffectRange, CSMWorld::Columns::ColumnId_EffectRange, false }, { CSMWorld::ColumnBase::Display_EffectId, CSMWorld::Columns::ColumnId_EffectId, false }, { CSMWorld::ColumnBase::Display_PartRefType, CSMWorld::Columns::ColumnId_PartRefType, false }, From b3daa7d95681800091b5e7173afd6c917d04297a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 8 Oct 2015 12:47:23 +1100 Subject: [PATCH 233/441] Fix AiWander sub-table editing. Should resolve bugs #2888 and #2930. (cherry picked from commit 3bbcf6a9169dc24d47c4d10e24f3818681fc34c0) Conflicts: apps/opencs/model/world/columns.cpp apps/opencs/model/world/columns.hpp --- apps/opencs/model/world/columns.cpp | 11 ++- apps/opencs/model/world/columns.hpp | 11 ++- apps/opencs/model/world/refidadapterimp.hpp | 98 +++++++++++++-------- apps/opencs/model/world/refidcollection.cpp | 18 +++- 4 files changed, 99 insertions(+), 39 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 6adc66951..c06cc2618 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -249,7 +249,7 @@ namespace CSMWorld { ColumnId_AiWanderDist, "Wander Dist" }, { ColumnId_AiDuration, "Ai Duration" }, { ColumnId_AiWanderToD, "Wander ToD" }, - { ColumnId_AiWanderIdle, "Wander Idle" }, + //{ ColumnId_AiWanderIdle, "Wander Idle" }, { ColumnId_AiWanderRepeat, "Wander Repeat" }, { ColumnId_AiActivateName, "Activate" }, { ColumnId_AiTargetId, "Target ID" }, @@ -317,6 +317,15 @@ namespace CSMWorld { ColumnId_MaxAttack, "Max Attack" }, { ColumnId_CreatureMisc, "Creature Misc" }, + { ColumnId_Idle1, "Idle 1" }, + { ColumnId_Idle2, "Idle 2" }, + { ColumnId_Idle3, "Idle 3" }, + { ColumnId_Idle4, "Idle 4" }, + { ColumnId_Idle5, "Idle 5" }, + { ColumnId_Idle6, "Idle 6" }, + { ColumnId_Idle7, "Idle 7" }, + { ColumnId_Idle8, "Idle 8" }, + { ColumnId_SpellSrc, "From Race" }, { ColumnId_SpellCost, "Cast Cost" }, { ColumnId_SpellChance, "Cast Chance" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 1992f225a..d30b939e0 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -241,7 +241,7 @@ namespace CSMWorld ColumnId_AiWanderDist = 221, ColumnId_AiDuration = 222, ColumnId_AiWanderToD = 223, - ColumnId_AiWanderIdle = 224, + // unused ColumnId_AiWanderRepeat = 225, ColumnId_AiActivateName = 226, // use ColumnId_PosX, etc for AI destinations @@ -316,6 +316,15 @@ namespace CSMWorld ColumnId_MaxAttack = 284, ColumnId_CreatureMisc = 285, + ColumnId_Idle1 = 286, + ColumnId_Idle2 = 287, + ColumnId_Idle3 = 288, + ColumnId_Idle4 = 289, + ColumnId_Idle5 = 290, + ColumnId_Idle6 = 291, + ColumnId_Idle7 = 292, + ColumnId_Idle8 = 293, + ColumnId_SpellSrc = 90, ColumnId_SpellCost = 177, ColumnId_SpellChance = 254, diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 3da273c89..03da88ddc 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1435,6 +1435,8 @@ namespace CSMWorld virtual ~ActorAiRefIdAdapter() {} + // FIXME: should check if the AI package type is already in the list and use a default + // that wasn't used already (in extreme case do not add anything at all? virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { @@ -1518,6 +1520,7 @@ namespace CSMWorld switch (subColIndex) { case 0: + // FIXME: should more than one AI package type be allowed? Check vanilla switch (content.mType) { case ESM::AI_Wander: return 0; @@ -1545,47 +1548,52 @@ namespace CSMWorld else return QVariant(); case 4: // wander idle + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: if (content.mType == ESM::AI_Wander) - { - return static_cast(content.mWander.mIdle[0]); // FIXME: - } + return static_cast(content.mWander.mIdle[subColIndex-4]); else return QVariant(); - case 5: // wander repeat + case 12: // wander repeat if (content.mType == ESM::AI_Wander) return content.mWander.mShouldRepeat != 0; else return QVariant(); - case 6: // activate name + case 13: // activate name if (content.mType == ESM::AI_Activate) return QString(content.mActivate.mName.toString().c_str()); else return QVariant(); - case 7: // target id + case 14: // target id if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return QString(content.mTarget.mId.toString().c_str()); else return QVariant(); - case 8: // target cell + case 15: // target cell if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return QString::fromUtf8(content.mCellName.c_str()); else return QVariant(); - case 9: + case 16: if (content.mType == ESM::AI_Travel) return content.mTravel.mX; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mX; else return QVariant(); - case 10: + case 17: if (content.mType == ESM::AI_Travel) return content.mTravel.mY; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mY; else return QVariant(); - case 11: + case 18: if (content.mType == ESM::AI_Travel) return content.mTravel.mZ; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) @@ -1615,11 +1623,12 @@ namespace CSMWorld case 0: // ai package type switch (value.toInt()) { - case 0: content.mType = ESM::AI_Wander; - case 1: content.mType = ESM::AI_Travel; - case 2: content.mType = ESM::AI_Follow; - case 3: content.mType = ESM::AI_Escort; - case 4: content.mType = ESM::AI_Activate; + case 0: content.mType = ESM::AI_Wander; break; + case 1: content.mType = ESM::AI_Travel; break; + case 2: content.mType = ESM::AI_Follow; break; + case 3: content.mType = ESM::AI_Escort; break; + case 4: content.mType = ESM::AI_Activate; break; + default: return; // return without saving } break; // always save @@ -1628,6 +1637,8 @@ namespace CSMWorld content.mWander.mDistance = static_cast(value.toInt()); else return; // return without saving + + break; // always save case 2: if (content.mType == ESM::AI_Wander || content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) @@ -1639,62 +1650,77 @@ namespace CSMWorld content.mWander.mTimeOfDay = static_cast(value.toInt()); else return; // return without saving + + break; // always save case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: if (content.mType == ESM::AI_Wander) - break; // FIXME: idle + content.mWander.mIdle[subColIndex-4] = static_cast(value.toInt()); else return; // return without saving - case 5: + + break; // always save + case 12: if (content.mType == ESM::AI_Wander) - { content.mWander.mShouldRepeat = static_cast(value.toInt()); - break; - } - case 6: // NAME32 + else + return; // return without saving + + break; // always save + case 13: // NAME32 if (content.mType == ESM::AI_Activate) - { content.mActivate.mName.assign(value.toString().toUtf8().constData()); - break; - } else return; // return without saving - case 7: // NAME32 + + break; // always save + case 14: // NAME32 if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - { content.mTarget.mId.assign(value.toString().toUtf8().constData()); - break; - } else return; // return without saving - case 8: + + break; // always save + case 15: if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - { content.mCellName = std::string(value.toString().toUtf8().constData()); - break; - } else return; // return without saving - case 9: + + break; // always save + case 16: if (content.mType == ESM::AI_Travel) content.mTravel.mZ = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mZ = value.toFloat(); else return; // return without saving - case 10: + + break; // always save + case 17: if (content.mType == ESM::AI_Travel) content.mTravel.mZ = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mZ = value.toFloat(); else return; // return without saving - case 11: + + break; // always save + case 18: if (content.mType == ESM::AI_Travel) content.mTravel.mZ = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mZ = value.toFloat(); else return; // return without saving + + break; // always save default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } @@ -1704,7 +1730,7 @@ namespace CSMWorld virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { - return 12; + return 19; } virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index b775d8156..9ad980dad 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -203,8 +203,24 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) new RefIdColumn (Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer)); + + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiWanderIdle, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Idle5, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( From 71077fda6a91f484e8e178b99d647c3cfc33c78f Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 9 Oct 2015 06:29:50 +1100 Subject: [PATCH 234/441] Disable toolbar context menu. Should resolve bug #2953. (cherry picked from commit 64821b0785a306f90e4f2f3be9c6a8fa61d440eb) --- apps/opencs/view/doc/view.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 47a2ffd51..1f9762b31 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -474,6 +474,8 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to mOperations = new Operations; addDockWidget (Qt::BottomDockWidgetArea, mOperations); + setContextMenuPolicy(Qt::NoContextMenu); + updateTitle(); setupUi(); From aa31704d5d21fcb32b6e268e2aa1699280b15855 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 9 Oct 2015 12:14:22 +0200 Subject: [PATCH 235/441] deal with script execution from within a script (Fixes #2964) (cherry picked from commit 8eb6d337d556dce2f64698ac30ad0df881bf84e0) --- components/interpreter/interpreter.cpp | 55 +++++++++++++++++++++----- components/interpreter/interpreter.hpp | 7 ++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp index 4fd3a8d8c..e6e06370f 100644 --- a/components/interpreter/interpreter.cpp +++ b/components/interpreter/interpreter.cpp @@ -133,7 +133,34 @@ namespace Interpreter throw std::runtime_error (error.str()); } - Interpreter::Interpreter() + void Interpreter::begin() + { + if (mRunning) + { + mCallstack.push (mRuntime); + mRuntime.clear(); + } + else + { + mRunning = true; + } + } + + void Interpreter::end() + { + if (mCallstack.empty()) + { + mRuntime.clear(); + mRunning = false; + } + else + { + mRuntime = mCallstack.top(); + mCallstack.pop(); + } + } + + Interpreter::Interpreter() : mRunning (false) {} Interpreter::~Interpreter() @@ -203,19 +230,29 @@ namespace Interpreter { assert (codeSize>=4); - mRuntime.configure (code, codeSize, context); + begin(); - int opcodes = static_cast (code[0]); + try + { + mRuntime.configure (code, codeSize, context); - const Type_Code *codeBlock = code + 4; + int opcodes = static_cast (code[0]); - while (mRuntime.getPC()>=0 && mRuntime.getPC()=0 && mRuntime.getPC() +#include #include "runtime.hpp" #include "types.hpp" @@ -14,6 +15,8 @@ namespace Interpreter class Interpreter { + std::stack mCallstack; + bool mRunning; Runtime mRuntime; std::map mSegment0; std::map mSegment1; @@ -32,6 +35,10 @@ namespace Interpreter void abortUnknownSegment (Type_Code code); + void begin(); + + void end(); + public: Interpreter(); From ba9190ed5eef6324459a63768ea5f8a73c15c6bc Mon Sep 17 00:00:00 2001 From: "artemutin@yandex.ru" Date: Wed, 7 Oct 2015 12:39:19 +1000 Subject: [PATCH 236/441] Implemented line and col calculations in scriptsubview (cherry picked from commit e7a3f059aafb643238d95de8d34554747d7e74f2) --- apps/opencs/view/world/scriptsubview.cpp | 46 +++++++++++++++--------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index d99f789b3..35b9ccf0c 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -198,26 +198,38 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint) if (hint.empty()) return; - if (hint[0]=='l') - { - std::istringstream stream (hint.c_str()+1); - - char ignore; - int line; - int column; - - if (stream >> ignore >> line >> column) + size_t line = 0, column = 0; + std::istringstream stream (hint.c_str()+2); + switch(hint[0]){ + case 'R': + case 'r': { - QTextCursor cursor = mEditor->textCursor(); - - cursor.movePosition (QTextCursor::Start); - if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) - cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); - - mEditor->setFocus(); - mEditor->setTextCursor (cursor); + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); + QString source = mModel->data (index).toString(); + size_t pos; + stream >> pos >> pos; + + for (size_t i = 0; i <= pos; ++i){ + if (source[(unsigned) i] == '\n'){ + ++line; + column = i; + } + } + column = pos - column - (line > 0 ? 1 : 0); + break; } + case 'l': + stream >> line >> column; } + + QTextCursor cursor = mEditor->textCursor(); + + cursor.movePosition (QTextCursor::Start); + if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) + cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); + + mEditor->setFocus(); + mEditor->setTextCursor (cursor); } void CSVWorld::ScriptSubView::textChanged() From f44d7e97b05cf58c0949138b0b9bd9fbb61f85d2 Mon Sep 17 00:00:00 2001 From: "artemutin@yandex.ru" Date: Fri, 9 Oct 2015 21:52:12 +1000 Subject: [PATCH 237/441] Stream error handling, and other minor changes. (cherry picked from commit 4ca7b2660932efb06535f148a643a7e814f3a72d) --- apps/opencs/view/world/scriptsubview.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 35b9ccf0c..eb0c70656 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -198,28 +198,31 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint) if (hint.empty()) return; - size_t line = 0, column = 0; - std::istringstream stream (hint.c_str()+2); + unsigned line = 0, column = 0; + char c; + std::istringstream stream (hint.c_str()+1); switch(hint[0]){ case 'R': case 'r': { QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); QString source = mModel->data (index).toString(); - size_t pos; - stream >> pos >> pos; + unsigned pos, dummy; + if (!(stream >> c >> dummy >> pos) ) + return; - for (size_t i = 0; i <= pos; ++i){ - if (source[(unsigned) i] == '\n'){ + for (unsigned i = 0; i <= pos; ++i){ + if (source[i] == '\n'){ ++line; - column = i; + column = i+1; } } - column = pos - column - (line > 0 ? 1 : 0); + column = pos - column; break; } case 'l': - stream >> line >> column; + if (!(stream >> c >> line >> column)) + return; } QTextCursor cursor = mEditor->textCursor(); From 437cabf778bfed1a83bdb4758883a45ff86f2e7e Mon Sep 17 00:00:00 2001 From: "artemutin@yandex.ru" Date: Sun, 11 Oct 2015 21:49:10 +1000 Subject: [PATCH 238/441] If table dont use any filter, filter update now dont cause a reapply of empty filter (cherry picked from commit 33e12a99fad15ced87a9996c42ffeb84983be2a4) --- apps/opencs/view/filter/editwidget.cpp | 12 +++++++++++- apps/opencs/view/filter/editwidget.hpp | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index 657a47750..5fbc66464 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -7,7 +7,7 @@ #include "../../model/world/data.hpp" CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) -: QLineEdit (parent), mParser (data) +: QLineEdit (parent), mParser (data), mIsEmpty(true) { mPalette = palette(); connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); @@ -27,6 +27,16 @@ CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) void CSVFilter::EditWidget::textChanged (const QString& text) { + //no need to parse and apply filter if it was empty and now is empty too. + //e.g. - we modifiing content of filter with already opened some other (big) tables. + if (text.length() == 0){ + if (mIsEmpty) + return; + else + mIsEmpty = true; + }else + mIsEmpty = false; + if (mParser.parse (text.toUtf8().constData())) { setPalette (mPalette); diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp index a0f9f8919..5c3f1b09e 100644 --- a/apps/opencs/view/filter/editwidget.hpp +++ b/apps/opencs/view/filter/editwidget.hpp @@ -25,6 +25,7 @@ namespace CSVFilter CSMFilter::Parser mParser; QPalette mPalette; + bool mIsEmpty; public: From 91c4afe78c3893e5d5a848fc6ed2d73aaba8a69f Mon Sep 17 00:00:00 2001 From: "artemutin@yandex.ru" Date: Tue, 13 Oct 2015 23:44:23 +1000 Subject: [PATCH 239/441] Fixed filter reapplication on Description column change and on State column change. Moved column index search to widget constructor, rewrite slot with respect to multiple columns data change. (cherry picked from commit eae36f800585a9347e778b36a1010b2a05f576c4) --- apps/opencs/view/filter/editwidget.cpp | 12 ++++++++++-- apps/opencs/view/filter/editwidget.hpp | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index 5fbc66464..600fa4f3b 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -5,6 +5,8 @@ #include #include "../../model/world/data.hpp" +#include "../../model/world/idtablebase.hpp" +#include "../../model/world/columns.hpp" CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) : QLineEdit (parent), mParser (data), mIsEmpty(true) @@ -12,7 +14,8 @@ CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) mPalette = palette(); connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); - QAbstractItemModel *model = data.getTableModel (CSMWorld::UniversalId::Type_Filters); + const CSMWorld::IdTableBase *model = + static_cast (data.getTableModel (CSMWorld::UniversalId::Type_Filters)); connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)), this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)), @@ -23,6 +26,9 @@ CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (filterRowsInserted (const QModelIndex&, int, int)), Qt::QueuedConnection); + + mStateColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Modification); + mDescColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Description); } void CSVFilter::EditWidget::textChanged (const QString& text) @@ -55,7 +61,9 @@ void CSVFilter::EditWidget::textChanged (const QString& text) void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - textChanged (text()); + for (int i = topLeft.column(); i <= bottomRight.column(); ++i) + if (i != mStateColumnIndex && i != mDescColumnIndex) + textChanged (text()); } void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end) diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp index 5c3f1b09e..f672877d9 100644 --- a/apps/opencs/view/filter/editwidget.hpp +++ b/apps/opencs/view/filter/editwidget.hpp @@ -26,6 +26,8 @@ namespace CSVFilter CSMFilter::Parser mParser; QPalette mPalette; bool mIsEmpty; + int mStateColumnIndex; + int mDescColumnIndex; public: From 30de5c1c595c75aea4347217e41d6473ec7ae506 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 28 Oct 2015 11:18:48 +1100 Subject: [PATCH 240/441] Fix editing ingredient effects sub-table. Should resolve bug #2978. (cherry picked from commit 9f0e059a15106eb5fa8f1174eadd91076623e7d3) --- apps/opencs/model/world/refidadapterimp.cpp | 127 ++++++++++++++++++++ apps/opencs/model/world/refidadapterimp.hpp | 60 +++++++++ apps/opencs/model/world/refidcollection.cpp | 17 ++- 3 files changed, 203 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 64c18a99b..653b60ea2 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -57,6 +57,133 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData } +CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns) +: InventoryColumns (columns) {} + +CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns) +: InventoryRefIdAdapter (UniversalId::Type_Ingredient, columns), + mColumns(columns) +{} + +QVariant CSMWorld::IngredientRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, + int index) const +{ + const Record& record = static_cast&> ( + data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Ingredient))); + + if (column==mColumns.mEffects) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + return InventoryRefIdAdapter::getData (column, data, index); +} + +void CSMWorld::IngredientRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, + const QVariant& value) const +{ + InventoryRefIdAdapter::setData (column, data, index, value); + + return; +} + + +CSMWorld::IngredEffectRefIdAdapter::IngredEffectRefIdAdapter() +: mType(UniversalId::Type_Ingredient) +{} + +CSMWorld::IngredEffectRefIdAdapter::~IngredEffectRefIdAdapter() +{} + +void CSMWorld::IngredEffectRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::IngredEffectRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::IngredEffectRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + ESM::Ingredient ingredient = record.get(); + + ingredient.mData = + static_cast >&>(nestedTable).mNestedTable.at(0); + + record.setModified (ingredient); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::IngredEffectRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + if (subRowIndex < 0 || subRowIndex >= 4) + throw std::runtime_error ("index out of range"); + + switch (subColIndex) + { + case 0: return record.get().mData.mEffectID[subRowIndex]; + case 1: return record.get().mData.mSkills[subRowIndex]; + case 2: return record.get().mData.mAttributes[subRowIndex]; + default: + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + } +} + +void CSMWorld::IngredEffectRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + ESM::Ingredient ingredient = record.get(); + + if (subRowIndex < 0 || subRowIndex >= 4) + throw std::runtime_error ("index out of range"); + + switch(subColIndex) + { + case 0: ingredient.mData.mEffectID[subRowIndex] = value.toInt(); break; + case 1: ingredient.mData.mSkills[subRowIndex] = value.toInt(); break; + case 2: ingredient.mData.mAttributes[subRowIndex] = value.toInt(); break; + default: + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + } + + record.setModified (ingredient); +} + +int CSMWorld::IngredEffectRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 3; // effect, skill, attribute +} + +int CSMWorld::IngredEffectRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + return 4; // up to 4 effects +} + + CSMWorld::ApparatusRefIdAdapter::ApparatusRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *type, const RefIdColumn *quality) : InventoryRefIdAdapter (UniversalId::Type_Apparatus, columns), diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 03da88ddc..ad9c76264 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -346,6 +346,66 @@ namespace CSMWorld ///< If the data type does not match an exception is thrown. }; + struct IngredientColumns : public InventoryColumns + { + const RefIdColumn *mEffects; + + IngredientColumns (const InventoryColumns& columns); + }; + + class IngredientRefIdAdapter : public InventoryRefIdAdapter + { + IngredientColumns mColumns; + + public: + + IngredientRefIdAdapter (const IngredientColumns& columns); + + virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) + const; + + virtual void setData (const RefIdColumn *column, RefIdData& data, int index, + const QVariant& value) const; + ///< If the data type does not match an exception is thrown. + }; + + class IngredEffectRefIdAdapter : public NestedRefIdAdapterBase + { + UniversalId::Type mType; + + // not implemented + IngredEffectRefIdAdapter (const IngredEffectRefIdAdapter&); + IngredEffectRefIdAdapter& operator= (const IngredEffectRefIdAdapter&); + + public: + + IngredEffectRefIdAdapter(); + + virtual ~IngredEffectRefIdAdapter(); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + struct EnchantableColumns : public InventoryColumns { const RefIdColumn *mEnchantment; diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 9ad980dad..355075c47 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -72,6 +72,21 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mColumns.push_back (RefIdColumn (Columns::ColumnId_CoinValue, ColumnBase::Display_Integer)); inventoryColumns.mValue = &mColumns.back(); + IngredientColumns ingredientColumns (inventoryColumns); + mColumns.push_back (RefIdColumn (Columns::ColumnId_EffectList, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + ingredientColumns.mEffects = &mColumns.back(); + std::map ingredientEffectsMap; + ingredientEffectsMap.insert(std::make_pair(UniversalId::Type_Ingredient, + new IngredEffectRefIdAdapter ())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), ingredientEffectsMap)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + // nested table PotionColumns potionColumns (inventoryColumns); mColumns.push_back (RefIdColumn (Columns::ColumnId_EffectList, @@ -662,7 +677,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mAdapters.insert (std::make_pair (UniversalId::Type_Door, new DoorRefIdAdapter (nameColumns, openSound, closeSound))); mAdapters.insert (std::make_pair (UniversalId::Type_Ingredient, - new InventoryRefIdAdapter (UniversalId::Type_Ingredient, inventoryColumns))); + new IngredientRefIdAdapter (ingredientColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_CreatureLevelledList, new LevelledListRefIdAdapter ( UniversalId::Type_CreatureLevelledList, levListColumns))); From bb1a959c888a66e086ea1926421e3d679e2b63bf Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 28 Oct 2015 11:30:36 +1100 Subject: [PATCH 241/441] Fix skills and attributes being possible to add to irrelevant effects. Should resolve bug #2980. (cherry picked from commit 1a64b4072570e09a75e2ac250576589244ed8a3e) --- .../model/world/nestedcoladapterimp.hpp | 30 +++++++++++++++++-- apps/opencs/model/world/refidadapterimp.cpp | 30 +++++++++++++++++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 81c52588b..2fd569bd0 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -317,8 +317,34 @@ namespace CSMWorld else throw std::runtime_error("Magic effects ID unexpected value"); } - case 1: return effect.mSkill; - case 2: return effect.mAttribute; + case 1: + { + switch (effect.mEffectID) + { + case ESM::MagicEffect::DrainSkill: + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::AbsorbSkill: + return effect.mSkill; + default: + return QVariant(); + } + } + case 2: + { + switch (effect.mEffectID) + { + case ESM::MagicEffect::DrainAttribute: + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::RestoreAttribute: + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::AbsorbAttribute: + return effect.mAttribute; + default: + return QVariant(); + } + } case 3: { if (effect.mRange >=0 && effect.mRange <=2) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 653b60ea2..77836d79a 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -144,8 +144,34 @@ QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *c switch (subColIndex) { case 0: return record.get().mData.mEffectID[subRowIndex]; - case 1: return record.get().mData.mSkills[subRowIndex]; - case 2: return record.get().mData.mAttributes[subRowIndex]; + case 1: + { + switch (record.get().mData.mEffectID[subRowIndex]) + { + case ESM::MagicEffect::DrainSkill: + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::AbsorbSkill: + return record.get().mData.mSkills[subRowIndex]; + default: + return QVariant(); + } + } + case 2: + { + switch (record.get().mData.mEffectID[subRowIndex]) + { + case ESM::MagicEffect::DrainAttribute: + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::RestoreAttribute: + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::AbsorbAttribute: + return record.get().mData.mAttributes[subRowIndex]; + default: + return QVariant(); + } + } default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } From b595f2641ca557a38d02801fbeea0edbebf5b82f Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 28 Oct 2015 11:49:24 +1100 Subject: [PATCH 242/441] Fix issue where mandatory effects field was allowed to be empty (and vice versa) (cherry picked from commit 972193c7ebcb597dfdd5b38e72965efa1bb4e70f) --- apps/opencs/model/world/columnbase.cpp | 4 ++++ apps/opencs/model/world/columnbase.hpp | 4 ++++ apps/opencs/model/world/data.cpp | 8 ++++---- apps/opencs/model/world/refidcollection.cpp | 10 +++++----- apps/opencs/view/doc/viewmanager.cpp | 3 +++ 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 39232d442..1f16c9695 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -86,6 +86,10 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_InfoCondVar, Display_InfoCondComp, + Display_EffectSkill, + Display_EffectAttribute, + Display_IngredEffectId, + Display_None }; diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index e2871d4d8..67d79fdbe 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -124,6 +124,10 @@ namespace CSMWorld Display_String32, Display_LongString256, + Display_EffectSkill, // must display at least one, unlike Display_Skill + Display_EffectAttribute, // must display at least one, unlike Display_Attribute + Display_IngredEffectId, // display none allowed, unlike Display_EffectId + //top level columns that nest other columns Display_NestedHeader }; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 10297df5b..0b33632da 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -282,9 +282,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mSpells.getNestableColumn(index)->addColumn( @@ -398,9 +398,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mEnchantments.getNestableColumn(index)->addColumn( diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 355075c47..a5e813338 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -81,11 +81,11 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) new IngredEffectRefIdAdapter ())); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), ingredientEffectsMap)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); + new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_IngredEffectId)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); // nested table PotionColumns potionColumns (inventoryColumns); @@ -99,9 +99,9 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mColumns.back().addColumn( diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 51755c7e2..766051bca 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -104,6 +104,9 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_AiPackageType, CSMWorld::Columns::ColumnId_AiPackageType, false }, { CSMWorld::ColumnBase::Display_InfoCondFunc, CSMWorld::Columns::ColumnId_InfoCondFunc, false }, { CSMWorld::ColumnBase::Display_InfoCondComp, CSMWorld::Columns::ColumnId_InfoCondComp, false }, + { CSMWorld::ColumnBase::Display_IngredEffectId, CSMWorld::Columns::ColumnId_EffectId, true }, + { CSMWorld::ColumnBase::Display_EffectSkill, CSMWorld::Columns::ColumnId_Skill, false }, + { CSMWorld::ColumnBase::Display_EffectAttribute, CSMWorld::Columns::ColumnId_Attribute, false }, }; for (std::size_t i=0; i Date: Wed, 28 Oct 2015 20:30:30 +1100 Subject: [PATCH 243/441] Disable context menu for fixed size sub-tables. Should resolve bug #2932. (cherry picked from commit 80869d9bae8c472ec7d1dca38eb668cc32850352) Conflicts: apps/opencs/view/world/dialoguesubview.cpp apps/opencs/view/world/nestedtable.cpp apps/opencs/view/world/nestedtable.hpp --- apps/opencs/model/world/columnbase.hpp | 22 +++++++++-- apps/opencs/model/world/data.cpp | 6 ++- apps/opencs/model/world/refidadapterimp.cpp | 41 +++++++++++---------- apps/opencs/view/world/dialoguesubview.cpp | 18 +++++++-- apps/opencs/view/world/nestedtable.cpp | 31 ++++++++++------ apps/opencs/view/world/nestedtable.hpp | 3 +- 6 files changed, 81 insertions(+), 40 deletions(-) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 67d79fdbe..c40bd9663 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -12,6 +12,13 @@ namespace CSMWorld { + enum TableEditModes + { + TableEdit_None, // no editing + TableEdit_Full, // edit cells and add/remove rows + TableEdit_FixedRows // edit cells only + }; + struct ColumnBase { enum Roles @@ -190,8 +197,8 @@ namespace CSMWorld template struct NestedParentColumn : public Column { - NestedParentColumn (int id, int flags = ColumnBase::Flag_Dialogue) : Column (id, - ColumnBase::Display_NestedHeader, flags) + NestedParentColumn (int id, int flags = ColumnBase::Flag_Dialogue, bool fixedRows = false) + : Column (id, ColumnBase::Display_NestedHeader, flags), mFixedRows(fixedRows) {} virtual void set (Record& record, const QVariant& data) @@ -202,13 +209,20 @@ namespace CSMWorld virtual QVariant get (const Record& record) const { - return true; // required by IdTree::hasChildren() + // by default editable; also see IdTree::hasChildren() + if (mFixedRows) + return QVariant::fromValue(TableEditModes::TableEdit_FixedRows); + else + return QVariant::fromValue(TableEditModes::TableEdit_Full); } virtual bool isEditable() const { return true; } + + Private: + bool mFixedRows; }; struct NestedChildColumn : public NestableColumn @@ -223,4 +237,6 @@ namespace CSMWorld }; } +Q_DECLARE_METATYPE(CSMWorld::TableEditModes) + #endif diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 0b33632da..dfb2d99d3 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -205,7 +205,8 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_SpellId, ColumnBase::Display_Spell)); // Race attributes - mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceAttributes)); + mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceAttributes, + ColumnBase::Flag_Dialogue, true)); // fixed rows table index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceAttributeAdapter())); mRaces.getNestableColumn(index)->addColumn( @@ -216,7 +217,8 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Female, ColumnBase::Display_Integer)); // Race skill bonus - mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus)); + mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus, + ColumnBase::Flag_Dialogue, true)); // fixed rows table index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); mRaces.getNestableColumn(index)->addColumn( diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 77836d79a..ed288ee98 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -30,8 +30,9 @@ QVariant CSMWorld::PotionRefIdAdapter::getData (const RefIdColumn *column, const if (column==mAutoCalc) return record.get().mData.mAutoCalc!=0; + // to show nested tables in dialogue subview, see IdTree::hasChildren() if (column==mColumns.mEffects) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(TableEditModes::TableEdit_Full); return InventoryRefIdAdapter::getData (column, data, index); } @@ -72,7 +73,7 @@ QVariant CSMWorld::IngredientRefIdAdapter::getData (const RefIdColumn *column, c data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Ingredient))); if (column==mColumns.mEffects) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(TableEditModes::TableEdit_FixedRows); return InventoryRefIdAdapter::getData (column, data, index); } @@ -276,7 +277,7 @@ QVariant CSMWorld::ArmorRefIdAdapter::getData (const RefIdColumn *column, return record.get().mData.mArmor; if (column==mPartRef) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(TableEditModes::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } @@ -364,7 +365,7 @@ QVariant CSMWorld::ClothingRefIdAdapter::getData (const RefIdColumn *column, return record.get().mData.mType; if (column==mPartRef) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(TableEditModes::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } @@ -412,7 +413,7 @@ QVariant CSMWorld::ContainerRefIdAdapter::getData (const RefIdColumn *column, return (record.get().mFlags & ESM::Container::Respawn)!=0; if (column==mContent) - return true; // Required to show nested tables in dialogue subview + return QVariant::fromValue(TableEditModes::TableEdit_Full); return NameRefIdAdapter::getData (column, data, index); } @@ -481,13 +482,13 @@ QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, con return QString::fromUtf8 (record.get().mOriginal.c_str()); if (column==mColumns.mAttributes) - return true; // Required to show nested tables in dialogue subview + return QVariant::fromValue(TableEditModes::TableEdit_FixedRows); if (column==mColumns.mAttacks) - return true; // Required to show nested tables in dialogue subview + return QVariant::fromValue(TableEditModes::TableEdit_FixedRows); if (column==mColumns.mMisc) - return true; // Required to show nested items in dialogue subview + return QVariant::fromValue(TableEditModes::TableEdit_Full); std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -727,13 +728,13 @@ QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const Re if (column==mColumns.mAttributes || column==mColumns.mSkills) { if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) - return QVariant(QVariant::UserType); + return QVariant::fromValue(TableEditModes::TableEdit_None); else - return true; + return QVariant::fromValue(TableEditModes::TableEdit_FixedRows); } if (column==mColumns.mMisc) - return true; + return QVariant::fromValue(TableEditModes::TableEdit_Full); std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -1613,21 +1614,21 @@ QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const return record.get().mAiData.mAlarm; if (column==mActors.mInventory) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(TableEditModes::TableEdit_Full); if (column==mActors.mSpells) { if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) - return QVariant(QVariant::UserType); + return QVariant::fromValue(TableEditModes::TableEdit_None); else - return true; + return QVariant::fromValue(TableEditModes::TableEdit_Full); } if (column==mActors.mDestinations) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(TableEditModes::TableEdit_Full); if (column==mActors.mAiPackages) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(TableEditModes::TableEdit_Full); std::map::const_iterator iter = mActors.mServices.find (column); @@ -1794,16 +1795,16 @@ QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, c return record.get().mAiData.mAlarm; if (column==mActors.mInventory) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(TableEditModes::TableEdit_Full); if (column==mActors.mSpells) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(TableEditModes::TableEdit_Full); if (column==mActors.mDestinations) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(TableEditModes::TableEdit_Full); if (column==mActors.mAiPackages) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(TableEditModes::TableEdit_Full); std::map::const_iterator iter = mActors.mServices.find (column); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index b8683d2fe..ed023ff7c 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -564,8 +564,20 @@ void CSVWorld::EditWidget::remake(int row) static_cast (mTable->data (mTable->index (row, typeColumn)).toInt()), mTable->data (mTable->index (row, idColumn)).toString().toUtf8().constData()); - bool editable = mTable->index(row, i).data().type() != QVariant::UserType; - NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this, editable); + bool editable = true; + bool fixedRows = false; + QVariant v = mTable->index(row, i).data(); + if (v.canConvert()) + { + assert (QString(v.typeName()) == "CSMWorld::TableEditModes"); + + if (v.value() == CSMWorld::TableEditModes::TableEdit_None) + editable = false; + else if (v.value() == CSMWorld::TableEditModes::TableEdit_FixedRows) + fixedRows = true; + } + + NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); if (!editable) { table->setEditTriggers(QAbstractItemView::NoEditTriggers); @@ -588,7 +600,7 @@ void CSVWorld::EditWidget::remake(int row) new QLabel (mTable->headerData (i, Qt::Horizontal, Qt::DisplayRole).toString(), mMainWidget); label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); - if(mTable->index(row, i).data().type() == QVariant::UserType) + if(!editable) label->setEnabled(false); tablesLayout->addWidget(label); diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 89b171891..23d566439 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -17,9 +17,12 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, QWidget* parent, - bool editable) + bool editable, + bool fixedRows) : DragRecordTable(document, parent), - mEditIdAction(0), + mAddNewRowAction(NULL), + mRemoveRowAction(NULL), + mEditIdAction(NULL), mModel(model) { mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); @@ -53,15 +56,18 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, if (editable) { - mAddNewRowAction = new QAction (tr ("Add new row"), this); + if (!fixedRows) + { + mAddNewRowAction = new QAction (tr ("Add new row"), this); - connect(mAddNewRowAction, SIGNAL(triggered()), - this, SLOT(addNewRowActionTriggered())); + connect(mAddNewRowAction, SIGNAL(triggered()), + this, SLOT(addNewRowActionTriggered())); - mRemoveRowAction = new QAction (tr ("Remove row"), this); + mRemoveRowAction = new QAction (tr ("Remove row"), this); - connect(mRemoveRowAction, SIGNAL(triggered()), - this, SLOT(removeRowActionTriggered())); + connect(mRemoveRowAction, SIGNAL(triggered()), + this, SLOT(removeRowActionTriggered())); + } mEditIdAction = new TableEditIdAction(*this, this); connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editCell())); @@ -92,10 +98,13 @@ void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) menu.addSeparator(); } - if (selectionModel()->selectedRows().size() == 1) - menu.addAction(mRemoveRowAction); + if (mAddNewRowAction && mRemoveRowAction) + { + if (selectionModel()->selectedRows().size() == 1) + menu.addAction(mRemoveRowAction); - menu.addAction(mAddNewRowAction); + menu.addAction(mAddNewRowAction); + } menu.exec (event->globalPos()); } diff --git a/apps/opencs/view/world/nestedtable.hpp b/apps/opencs/view/world/nestedtable.hpp index 23a925dca..765060ea5 100644 --- a/apps/opencs/view/world/nestedtable.hpp +++ b/apps/opencs/view/world/nestedtable.hpp @@ -39,7 +39,8 @@ namespace CSVWorld CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, QWidget* parent = NULL, - bool editable = true); + bool editable = true, + bool fixedRows = false); virtual std::vector getDraggedRecords() const; From 0ec4af42100739d6823cb5927d7d0613445f11f3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 28 Oct 2015 20:52:07 +1100 Subject: [PATCH 244/441] Re-add mistakenly removed line and fix a silly typo. (cherry picked from commit 77471d1592611fe08b65e6b34b7e656d78753944) --- apps/opencs/model/world/columnbase.hpp | 2 +- apps/opencs/view/world/dialoguesubview.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index c40bd9663..8a0a70644 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -221,7 +221,7 @@ namespace CSMWorld return true; } - Private: + private: bool mFixedRows; }; diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index ed023ff7c..67b486363 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -577,7 +577,9 @@ void CSVWorld::EditWidget::remake(int row) fixedRows = true; } - NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); + NestedTable* table = + new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); + table->resizeColumnsToContents(); if (!editable) { table->setEditTriggers(QAbstractItemView::NoEditTriggers); From 245357c9b2d18ad170951a956a6cc51c82b003fa Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 28 Oct 2015 23:33:24 +1100 Subject: [PATCH 245/441] Move TableEditModes enum inside a class scope. (cherry picked from commit 107ccd84d4f411a825c2f095bae7f84e65f8609d) Conflicts: apps/opencs/model/world/refidadapterimp.cpp --- apps/opencs/model/world/columnbase.hpp | 20 ++++----- apps/opencs/model/world/refidadapterimp.cpp | 45 ++++++++++----------- apps/opencs/view/world/dialoguesubview.cpp | 8 ++-- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 8a0a70644..c75a3c2a1 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -12,15 +12,15 @@ namespace CSMWorld { - enum TableEditModes - { - TableEdit_None, // no editing - TableEdit_Full, // edit cells and add/remove rows - TableEdit_FixedRows // edit cells only - }; - struct ColumnBase { + enum TableEditModes + { + TableEdit_None, // no editing + TableEdit_Full, // edit cells and add/remove rows + TableEdit_FixedRows // edit cells only + }; + enum Roles { Role_Flags = Qt::UserRole, @@ -211,9 +211,9 @@ namespace CSMWorld { // by default editable; also see IdTree::hasChildren() if (mFixedRows) - return QVariant::fromValue(TableEditModes::TableEdit_FixedRows); + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); else - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); } virtual bool isEditable() const @@ -237,6 +237,6 @@ namespace CSMWorld }; } -Q_DECLARE_METATYPE(CSMWorld::TableEditModes) +Q_DECLARE_METATYPE(CSMWorld::ColumnBase::TableEditModes) #endif diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index ed288ee98..3e7dc3aca 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -6,7 +6,9 @@ #include #include +#include +#include "columnbase.hpp" #include "nestedtablewrapper.hpp" #include "usertype.hpp" #include "idtree.hpp" @@ -32,7 +34,7 @@ QVariant CSMWorld::PotionRefIdAdapter::getData (const RefIdColumn *column, const // to show nested tables in dialogue subview, see IdTree::hasChildren() if (column==mColumns.mEffects) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); return InventoryRefIdAdapter::getData (column, data, index); } @@ -69,11 +71,8 @@ CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumn QVariant CSMWorld::IngredientRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) const { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Ingredient))); - if (column==mColumns.mEffects) - return QVariant::fromValue(TableEditModes::TableEdit_FixedRows); + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); return InventoryRefIdAdapter::getData (column, data, index); } @@ -277,7 +276,7 @@ QVariant CSMWorld::ArmorRefIdAdapter::getData (const RefIdColumn *column, return record.get().mData.mArmor; if (column==mPartRef) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } @@ -365,7 +364,7 @@ QVariant CSMWorld::ClothingRefIdAdapter::getData (const RefIdColumn *column, return record.get().mData.mType; if (column==mPartRef) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } @@ -413,7 +412,7 @@ QVariant CSMWorld::ContainerRefIdAdapter::getData (const RefIdColumn *column, return (record.get().mFlags & ESM::Container::Respawn)!=0; if (column==mContent) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); return NameRefIdAdapter::getData (column, data, index); } @@ -482,13 +481,13 @@ QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, con return QString::fromUtf8 (record.get().mOriginal.c_str()); if (column==mColumns.mAttributes) - return QVariant::fromValue(TableEditModes::TableEdit_FixedRows); + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); if (column==mColumns.mAttacks) - return QVariant::fromValue(TableEditModes::TableEdit_FixedRows); + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); if (column==mColumns.mMisc) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -728,13 +727,13 @@ QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const Re if (column==mColumns.mAttributes || column==mColumns.mSkills) { if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) - return QVariant::fromValue(TableEditModes::TableEdit_None); + return QVariant::fromValue(ColumnBase::TableEdit_None); else - return QVariant::fromValue(TableEditModes::TableEdit_FixedRows); + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); } if (column==mColumns.mMisc) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -1614,21 +1613,21 @@ QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const return record.get().mAiData.mAlarm; if (column==mActors.mInventory) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mSpells) { if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) - return QVariant::fromValue(TableEditModes::TableEdit_None); + return QVariant::fromValue(ColumnBase::TableEdit_None); else - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); } if (column==mActors.mDestinations) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mAiPackages) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mActors.mServices.find (column); @@ -1795,16 +1794,16 @@ QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, c return record.get().mAiData.mAlarm; if (column==mActors.mInventory) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mSpells) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mDestinations) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mAiPackages) - return QVariant::fromValue(TableEditModes::TableEdit_Full); + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mActors.mServices.find (column); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 67b486363..14b7fcac3 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -567,13 +567,13 @@ void CSVWorld::EditWidget::remake(int row) bool editable = true; bool fixedRows = false; QVariant v = mTable->index(row, i).data(); - if (v.canConvert()) + if (v.canConvert()) { - assert (QString(v.typeName()) == "CSMWorld::TableEditModes"); + assert (QString(v.typeName()) == "CSMWorld::ColumnBase::TableEditModes"); - if (v.value() == CSMWorld::TableEditModes::TableEdit_None) + if (v.value() == CSMWorld::ColumnBase::TableEdit_None) editable = false; - else if (v.value() == CSMWorld::TableEditModes::TableEdit_FixedRows) + else if (v.value() == CSMWorld::ColumnBase::TableEdit_FixedRows) fixedRows = true; } From 894f3a9418ed70d8a111ce7b5c2fb276ce7a12e1 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 31 Oct 2015 20:42:42 +1100 Subject: [PATCH 246/441] Fix saving when only topic info was modified (topic itself unchanged) (cherry picked from commit 78c735adc609dda06954746b31cea4289a7060ad) --- apps/opencs/model/doc/savingstages.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index c6d8a8cb3..138e84b9a 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -134,10 +134,21 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message state==CSMWorld::RecordBase::State_ModifiedOnly || infoModified) { - mState.getWriter().startRecord (topic.mModified.sRecordId); - mState.getWriter().writeHNCString ("NAME", topic.mModified.mId); - topic.mModified.save (mState.getWriter()); - mState.getWriter().endRecord (topic.mModified.sRecordId); + if (infoModified && state != CSMWorld::RecordBase::State_Modified + && state != CSMWorld::RecordBase::State_ModifiedOnly) + { + mState.getWriter().startRecord (topic.mBase.sRecordId); + mState.getWriter().writeHNCString ("NAME", topic.mBase.mId); + topic.mBase.save (mState.getWriter()); + mState.getWriter().endRecord (topic.mBase.sRecordId); + } + else + { + mState.getWriter().startRecord (topic.mModified.sRecordId); + mState.getWriter().writeHNCString ("NAME", topic.mModified.mId); + topic.mModified.save (mState.getWriter()); + mState.getWriter().endRecord (topic.mModified.sRecordId); + } // write modified selected info records for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; From 26195dc5fe9e5edb01bfbba88dedf287ca323dba Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 31 Oct 2015 20:45:16 +1100 Subject: [PATCH 247/441] Rename a variable to make it less confusing. (cherry picked from commit 7c007d9c6d530126075165766a8c497bd3de7d62) --- apps/opencs/model/doc/savingstages.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 138e84b9a..3fba2cd85 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -154,14 +154,14 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - CSMWorld::RecordBase::State state = iter->mState; + CSMWorld::RecordBase::State infoState = iter->mState; - if (state==CSMWorld::RecordBase::State_Deleted) + if (infoState==CSMWorld::RecordBase::State_Deleted) { /// \todo wrote record with delete flag } - else if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + else if (infoState==CSMWorld::RecordBase::State_Modified || + infoState==CSMWorld::RecordBase::State_ModifiedOnly) { ESM::DialInfo info = iter->get(); info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); From a19a1dabe2ebcbf19d8cc69b5230c67474795e6d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 1 Nov 2015 11:23:28 +1100 Subject: [PATCH 248/441] Add a check for scale value of 0. Should resolve bug #2880. (cherry picked from commit 819fecd08ec163e3f6b94ca40330f68214d54579) --- apps/opencs/model/tools/referenceablecheck.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index 6b323547f..336a5e713 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -468,6 +468,9 @@ void CSMTools::ReferenceableCheckStage::creatureCheck ( if (creature.mData.mGold < 0) //It seems that this is for gold in merchant creatures messages.push_back (std::make_pair (id, creature.mId + " has negative gold ")); + if (creature.mScale == 0) + messages.push_back (std::make_pair (id, creature.mId + " has zero scale value")); + // Check that mentioned scripts exist scriptCheck(creature, messages, id.toString()); } From 6d38d974708271fec3076f9766c52abb1de43d60 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 2 Nov 2015 06:43:20 +1100 Subject: [PATCH 249/441] Fix some sub-tables becoming uneditable since commit 80869d (cherry picked from commit 4af469511da373c48d7b3eaa0b4e1eb37971558c) Conflicts: apps/opencs/model/world/refidadapterimp.hpp --- apps/opencs/model/world/refidadapterimp.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index ad9c76264..170b5ebc0 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1983,7 +1983,7 @@ namespace CSMWorld int index) const { if (column==mLevList.mLevList || column == mLevList.mNestedListLevList) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return BaseRefIdAdapter::getData (column, data, index); } From 12a8692cdbb3b3dfed428bd1df130f5ce7d149b6 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 2 Nov 2015 06:57:24 +1100 Subject: [PATCH 250/441] Fix include file issue. (cherry picked from commit 7f477e2fae47bdfe78ac17b2622ed2fdd844fd98) --- apps/opencs/model/world/refidadapterimp.cpp | 1 - apps/opencs/model/world/refidadapterimp.hpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 3e7dc3aca..a268cb5f0 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -8,7 +8,6 @@ #include #include -#include "columnbase.hpp" #include "nestedtablewrapper.hpp" #include "usertype.hpp" #include "idtree.hpp" diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 170b5ebc0..a2b4df3c4 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -15,6 +15,7 @@ #include #include +#include "columnbase.hpp" #include "record.hpp" #include "refiddata.hpp" #include "universalid.hpp" From 007c733578f249a4d9c683c81c1fb8fc37216820 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 31 May 2015 07:14:37 +1000 Subject: [PATCH 251/441] Resolve merge issues and add back user preference setting for auto selecting a modified record. (cherry picked from commit 321b1b5106a47500bffd4c78a66d3d6f44a06f5b) Conflicts: apps/opencs/model/settings/usersettings.cpp --- apps/opencs/model/settings/usersettings.cpp | 5 +++++ apps/opencs/view/world/tablesubview.cpp | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index a40a790b7..5cf3bd7a7 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -243,6 +243,11 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() jumpToAdded->setDefaultValue (defaultValue); jumpToAdded->setDeclaredValues (jumpValues); + Setting *jumpToModified = createSetting (Type_CheckBox, "jump-to-modified", "Jump to modified Record"); + jumpToModified->setDefaultValue ("true"); + jumpToModified->setToolTip ("Whether to jump to the modified record. This setting effects the instances table only." + "\nCan be useful in finding the moved or modified object instance while 3D editing."); + Setting *extendedConfig = createSetting (Type_CheckBox, "extended-config", "Manually specify affected record types for an extended delete/revert"); extendedConfig->setDefaultValue("false"); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 7a2680434..81f78cdad 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -54,7 +54,8 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D autoJump->setToolTip ("Whether to jump to the modified record." "\nCan be useful in finding the moved or modified" "\nobject instance while 3D editing."); - autoJump->setCheckState(Qt::Unchecked); + autoJump->setCheckState( + userSettings.settingValue ("table-input/jump-to-modified") == "true" ? Qt::Checked : Qt::Unchecked); connect(autoJump, SIGNAL (stateChanged(int)), mTable, SLOT (jumpAfterModChanged(int))); optHLayout->insertWidget(0, autoJump); optHLayout->insertWidget(1, added); From 04c5c0d82a1acde5e558563c113af285951e985a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 8 Nov 2015 06:02:53 +1100 Subject: [PATCH 252/441] Move NPC autocal code out to a separate class. --- apps/opencs/CMakeLists.txt | 4 +- apps/opencs/model/world/data.cpp | 365 +------------------ apps/opencs/model/world/data.hpp | 25 +- apps/opencs/model/world/npcautocalc.cpp | 367 ++++++++++++++++++++ apps/opencs/model/world/npcautocalc.hpp | 75 ++++ apps/opencs/model/world/refidadapterimp.cpp | 15 +- 6 files changed, 466 insertions(+), 385 deletions(-) create mode 100644 apps/opencs/model/world/npcautocalc.cpp create mode 100644 apps/opencs/model/world/npcautocalc.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 41d44ca18..30c78e710 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -19,7 +19,7 @@ opencs_hdrs_noqt (model/doc opencs_units (model/world idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel - pathgridcommands + pathgridcommands npcautocalc ) @@ -36,7 +36,7 @@ opencs_hdrs_noqt (model/world opencs_units (model/tools - tools reportmodel mergeoperation + tools reportmodel mergeoperation ) opencs_units_noqt (model/tools diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index dfb2d99d3..84cb505c0 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -10,10 +10,6 @@ #include #include -#include -#include -#include - #include "idtable.hpp" #include "idtree.hpp" #include "columnimp.hpp" @@ -23,70 +19,7 @@ #include "resourcetable.hpp" #include "nestedcoladapterimp.hpp" #include "npcstats.hpp" - -namespace -{ - class CSStore : public AutoCalc::StoreCommon - { - const CSMWorld::IdCollection& mGmstTable; - const CSMWorld::IdCollection& mSkillTable; - const CSMWorld::IdCollection& mMagicEffectTable; - const CSMWorld::NestedIdCollection& mSpells; - - public: - - CSStore(const CSMWorld::IdCollection& gmst, - const CSMWorld::IdCollection& skills, - const CSMWorld::IdCollection& magicEffects, - const CSMWorld::NestedIdCollection& spells) - : mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpells(spells) - { } - - ~CSStore() {} - - virtual int findGmstInt(const std::string& name) const - { - return mGmstTable.getRecord(name).get().getInt(); - } - - virtual float findGmstFloat(const std::string& name) const - { - return mGmstTable.getRecord(name).get().getFloat(); - } - - virtual const ESM::Skill *findSkill(int index) const - { - // if the skill does not exist, throws std::runtime_error ("invalid ID: " + id) - return &mSkillTable.getRecord(ESM::Skill::indexToId(index)).get(); - } - - virtual const ESM::MagicEffect* findMagicEffect(int id) const - { - // if the magic effect does not exist, throws std::runtime_error ("invalid ID: " + id) - return &mMagicEffectTable.getRecord(ESM::MagicEffect::indexToId((short)id)).get(); - } - - virtual void getSpells(std::vector& spells) - { - // prepare data in a format used by OpenMW store - for (int index = 0; index < mSpells.getSize(); ++index) - spells.push_back(const_cast(&mSpells.getRecord(index).get())); - } - }; - - unsigned short autoCalculateMana(AutoCalc::StatsBase& stats) - { - return stats.getBaseAttribute(ESM::Attribute::Intelligence) * 2; - } - - unsigned short autoCalculateFatigue(AutoCalc::StatsBase& stats) - { - return stats.getBaseAttribute(ESM::Attribute::Strength) - + stats.getBaseAttribute(ESM::Attribute::Willpower) - + stats.getBaseAttribute(ESM::Attribute::Agility) - + stats.getBaseAttribute(ESM::Attribute::Endurance); - } -} +#include "npcautocalc.hpp" void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update) { @@ -129,8 +62,9 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec } CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) -: mEncoder (encoding), mPathgrids (mCells), mReferenceables(self()), mRefs (mCells), - mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0) +: mEncoder (encoding), mPathgrids (mCells), mReferenceables (self()), mRefs (mCells), + mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), + mNpcAutoCalc (0) { int index = 0; @@ -621,32 +555,18 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc CSMWorld::IdTree *objects = static_cast(getTableModel(UniversalId::Type_Referenceable)); - connect (gmsts, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (gmstDataChanged (const QModelIndex&, const QModelIndex&))); - connect (skills, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (skillDataChanged (const QModelIndex&, const QModelIndex&))); - connect (classes, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (classDataChanged (const QModelIndex&, const QModelIndex&))); - connect (races, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (raceDataChanged (const QModelIndex&, const QModelIndex&))); - connect (objects, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (npcDataChanged (const QModelIndex&, const QModelIndex&))); - connect (this, SIGNAL (updateNpcAutocalc (int, const std::string&)), - objects, SLOT (updateNpcAutocalc (int, const std::string&))); - connect (this, SIGNAL (cacheNpcStats (const std::string&, NpcStats*)), - this, SLOT (cacheNpcStatsEvent (const std::string&, NpcStats*))); + mNpcAutoCalc = new NpcAutoCalc (self(), gmsts, skills, classes, races, objects); mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files } CSMWorld::Data::~Data() { - clearNpcStatsCache(); - for (std::vector::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter) delete *iter; delete mReader; + delete mNpcAutoCalc; } const CSMWorld::IdCollection& CSMWorld::Data::getGlobals() const @@ -946,6 +866,11 @@ void CSMWorld::Data::setMetaData (const MetaData& metaData) mMetaData.setRecord (0, record); } +const CSMWorld::NpcAutoCalc& CSMWorld::Data::getNpcAutoCalc() const +{ + return *mNpcAutoCalc; +} + QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id) { std::map::iterator iter = mModelIndex.find (id.getType()); @@ -1297,271 +1222,3 @@ const CSMWorld::Data& CSMWorld::Data::self () { return *this; } - -void CSMWorld::Data::skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) -{ - // mData.mAttribute (affects attributes skill bonus autocalc) - // mData.mSpecialization (affects skills autocalc) - CSMWorld::IdTable *skillModel = - static_cast(getTableModel(CSMWorld::UniversalId::Type_Skill)); - - int attributeColumn = skillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute); - int specialisationColumn = skillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation); - - if ((topLeft.column() <= attributeColumn && attributeColumn <= bottomRight.column()) - || (topLeft.column() <= specialisationColumn && specialisationColumn <= bottomRight.column())) - { - clearNpcStatsCache(); - - std::string empty; - emit updateNpcAutocalc(0/*all*/, empty); - } -} - -void CSMWorld::Data::classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) -{ - // update autocalculated attributes/skills of every NPC with matching class - // - mData.mAttribute[2] - // - mData.mSkills[5][2] - // - mData.mSpecialization - CSMWorld::IdTable *classModel = - static_cast(getTableModel(CSMWorld::UniversalId::Type_Class)); - - int attribute1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute1); // +1 - int majorSkill1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_MajorSkill1); // +4 - int minorSkill1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_MinorSkill1); // +4 - int specialisationColumn = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation); - - if ((topLeft.column() > attribute1Column+1 || attribute1Column > bottomRight.column()) - && (topLeft.column() > majorSkill1Column+4 || majorSkill1Column > bottomRight.column()) - && (topLeft.column() > minorSkill1Column+4 || minorSkill1Column > bottomRight.column()) - && (topLeft.column() > specialisationColumn || specialisationColumn > bottomRight.column())) - { - return; - } - - // get the affected class - int idColumn = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); - for (int classRow = topLeft.row(); classRow <= bottomRight.row(); ++classRow) - { - clearNpcStatsCache(); - - std::string classId = - classModel->data(classModel->index(classRow, idColumn)).toString().toUtf8().constData(); - emit updateNpcAutocalc(1/*class*/, classId); - } -} - -void CSMWorld::Data::raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) -{ - // affects racial bonus attributes & skills - // - mData.mAttributeValues[] - // - mData.mBonus[].mBonus - // - mPowers.mList[] - CSMWorld::IdTree *raceModel = - static_cast(getTableModel(CSMWorld::UniversalId::Type_Race)); - - int attrColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceAttributes); - int bonusColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceSkillBonus); - int powersColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_PowerList); - - bool match = false; - int raceRow = topLeft.row(); - int raceEnd = bottomRight.row(); - if (topLeft.parent().isValid() && bottomRight.parent().isValid()) - { - if ((topLeft.parent().column() <= attrColumn && attrColumn <= bottomRight.parent().column()) - || (topLeft.parent().column() <= bonusColumn && bonusColumn <= bottomRight.parent().column()) - || (topLeft.parent().column() <= powersColumn && powersColumn <= bottomRight.parent().column())) - { - match = true; // TODO: check for specific nested column? - raceRow = topLeft.parent().row(); - raceEnd = bottomRight.parent().row(); - } - } - else - { - if ((topLeft.column() <= attrColumn && attrColumn <= bottomRight.column()) - || (topLeft.column() <= bonusColumn && bonusColumn <= bottomRight.column()) - || (topLeft.column() <= powersColumn && powersColumn <= bottomRight.column())) - { - match = true; // maybe the whole table changed - } - } - - if (!match) - return; - - // update autocalculated attributes/skills of every NPC with matching race - int idColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); - for (; raceRow <= raceEnd; ++raceRow) - { - clearNpcStatsCache(); - - std::string raceId = - raceModel->data(raceModel->index(raceRow, idColumn)).toString().toUtf8().constData(); - emit updateNpcAutocalc(2/*race*/, raceId); - } -} - -void CSMWorld::Data::npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) -{ - // TODO: for now always recalculate - clearNpcStatsCache(); -} - -void CSMWorld::Data::gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) -{ - static const QStringList gmsts(QStringList()<< "fNPCbaseMagickaMult" << "fAutoSpellChance" - << "fEffectCostMult" << "iAutoSpellAlterationMax" << "iAutoSpellConjurationMax" - << "iAutoSpellDestructionMax" << "iAutoSpellIllusionMax" << "iAutoSpellMysticismMax" - << "iAutoSpellRestorationMax" << "iAutoSpellTimesCanCast" << "iAutoSpellAttSkillMin"); - - bool match = false; - for (int row = topLeft.row(); row <= bottomRight.row(); ++row) - { - if (gmsts.contains(mGmsts.getRecord(row).get().mId.c_str())) - { - match = true; - break; - } - } - - if (!match) - return; - - clearNpcStatsCache(); - - std::string empty; - emit updateNpcAutocalc(0/*all*/, empty); -} - -void CSMWorld::Data::clearNpcStatsCache () -{ - for (std::map::iterator it (mNpcStatCache.begin()); - it != mNpcStatCache.end(); ++it) - delete it->second; - - mNpcStatCache.clear(); -} - -CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const -{ - CSMWorld::NpcStats * cachedStats = getCachedNpcData (npc.mId); - if (cachedStats) - return cachedStats; - - int raceIndex = mRaces.searchId(npc.mRace); - int classIndex = mClasses.searchId(npc.mClass); - // this can happen when creating a new game from scratch - if (raceIndex == -1 || classIndex == -1) - return 0; - - const ESM::Race *race = &mRaces.getRecord(raceIndex).get(); - const ESM::Class *class_ = &mClasses.getRecord(classIndex).get(); - - bool autoCalc = npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; - short level = npc.mNpdt52.mLevel; - if (autoCalc) - level = npc.mNpdt12.mLevel; - - std::auto_ptr stats (new CSMWorld::NpcStats()); - - CSStore store(mGmsts, mSkills, mMagicEffects, mSpells); - - if (autoCalc) - { - AutoCalc::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store); - - stats->setHealth(autoCalculateHealth(level, class_, *stats)); - stats->setMana(autoCalculateMana(*stats)); - stats->setFatigue(autoCalculateFatigue(*stats)); - - AutoCalc::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store); - - AutoCalc::autoCalculateSpells(race, *stats, &store); - } - else - { - for (std::vector::const_iterator it = npc.mSpells.mList.begin(); - it != npc.mSpells.mList.end(); ++it) - { - stats->addSpell(*it); - } - } - - // update spell info - const std::vector &racePowers = race->mPowers.mList; - for (unsigned int i = 0; i < racePowers.size(); ++i) - { - int type = -1; - int spellIndex = mSpells.searchId(racePowers[i]); - if (spellIndex != -1) - type = mSpells.getRecord(spellIndex).get().mData.mType; - stats->addPowers(racePowers[i], type); - } - // cost/chance - int skills[ESM::Skill::Length]; - if (autoCalc) - for (int i = 0; i< ESM::Skill::Length; ++i) - skills[i] = stats->getBaseSkill(i); - else - for (int i = 0; i< ESM::Skill::Length; ++i) - skills[i] = npc.mNpdt52.mSkills[i]; - - int attributes[ESM::Attribute::Length]; - if (autoCalc) - for (int i = 0; i< ESM::Attribute::Length; ++i) - attributes[i] = stats->getBaseAttribute(i); - else - { - attributes[ESM::Attribute::Strength] = npc.mNpdt52.mStrength; - attributes[ESM::Attribute::Willpower] = npc.mNpdt52.mWillpower; - attributes[ESM::Attribute::Agility] = npc.mNpdt52.mAgility; - attributes[ESM::Attribute::Speed] = npc.mNpdt52.mSpeed; - attributes[ESM::Attribute::Endurance] = npc.mNpdt52.mEndurance; - attributes[ESM::Attribute::Personality] = npc.mNpdt52.mPersonality; - attributes[ESM::Attribute::Luck] = npc.mNpdt52.mLuck; - } - - const std::vector& spells = stats->spells(); - for (std::vector::const_iterator it = spells.begin(); it != spells.end(); ++it) - { - int cost = -1; - int spellIndex = mSpells.searchId((*it).mName); - const ESM::Spell* spell = 0; - if (spellIndex != -1) - { - spell = &mSpells.getRecord(spellIndex).get(); - cost = spell->mData.mCost; - - int school; - float skillTerm; - AutoCalc::calcWeakestSchool(spell, skills, school, skillTerm, &store); - float chance = calcAutoCastChance(spell, skills, attributes, school, &store); - - stats->addCostAndChance((*it).mName, cost, (int)ceil(chance)); // percent - } - } - - if (stats.get() == 0) - return 0; - - CSMWorld::NpcStats *result = stats.release(); - emit cacheNpcStats (npc.mId, result); - return result; -} - -void CSMWorld::Data::cacheNpcStatsEvent (const std::string& id, CSMWorld::NpcStats *stats) -{ - mNpcStatCache[id] = stats; -} - -CSMWorld::NpcStats* CSMWorld::Data::getCachedNpcData (const std::string& id) const -{ - std::map::const_iterator it = mNpcStatCache.find(id); - if (it != mNpcStatCache.end()) - return it->second; - else - return 0; -} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 944a636f0..3d3996a1d 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -62,6 +62,7 @@ namespace CSMWorld class ResourcesManager; class Resources; class NpcStats; + class NpcAutoCalc; class Data : public QObject { @@ -109,7 +110,7 @@ namespace CSMWorld std::vector > mReaders; - std::map mNpcStatCache; + NpcAutoCalc *mNpcAutoCalc; // not implemented Data (const Data&); @@ -282,37 +283,17 @@ namespace CSMWorld int count (RecordBase::State state) const; ///< Return number of top-level records with the given \a state. - NpcStats* npcAutoCalculate (const ESM::NPC& npc) const; - - NpcStats* getCachedNpcData (const std::string& id) const; + const NpcAutoCalc& getNpcAutoCalc() const; signals: void idListChanged(); - // refresh NPC dialogue subviews via object table model - void updateNpcAutocalc (int type, const std::string& id); - - void cacheNpcStats (const std::string& id, NpcStats *stats) const; - private slots: void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void rowsChanged (const QModelIndex& parent, int start, int end); - - // for autocalc updates when gmst/race/class/skils tables change - void gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - - void raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - - void classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - - void skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - - void npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - - void cacheNpcStatsEvent (const std::string& id, NpcStats *stats); }; } diff --git a/apps/opencs/model/world/npcautocalc.cpp b/apps/opencs/model/world/npcautocalc.cpp new file mode 100644 index 000000000..21eb82266 --- /dev/null +++ b/apps/opencs/model/world/npcautocalc.cpp @@ -0,0 +1,367 @@ +#include "npcautocalc.hpp" + +#include + +#include +#include +#include + +#include "npcstats.hpp" +#include "data.hpp" +#include "idtable.hpp" +#include "idtree.hpp" + +namespace +{ + class CSStore : public AutoCalc::StoreCommon + { + const CSMWorld::IdCollection& mGmstTable; + const CSMWorld::IdCollection& mSkillTable; + const CSMWorld::IdCollection& mMagicEffectTable; + const CSMWorld::NestedIdCollection& mSpells; + + public: + + CSStore(const CSMWorld::IdCollection& gmst, + const CSMWorld::IdCollection& skills, + const CSMWorld::IdCollection& magicEffects, + const CSMWorld::NestedIdCollection& spells) + : mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpells(spells) + {} + + ~CSStore() {} + + virtual int findGmstInt(const std::string& name) const + { + return mGmstTable.getRecord(name).get().getInt(); + } + + virtual float findGmstFloat(const std::string& name) const + { + return mGmstTable.getRecord(name).get().getFloat(); + } + + virtual const ESM::Skill *findSkill(int index) const + { + // if the skill does not exist, throws std::runtime_error ("invalid ID: " + id) + return &mSkillTable.getRecord(ESM::Skill::indexToId(index)).get(); + } + + virtual const ESM::MagicEffect* findMagicEffect(int id) const + { + // if the magic effect does not exist, throws std::runtime_error ("invalid ID: " + id) + return &mMagicEffectTable.getRecord(ESM::MagicEffect::indexToId((short)id)).get(); + } + + virtual void getSpells(std::vector& spells) + { + // prepare data in a format used by OpenMW store + for (int index = 0; index < mSpells.getSize(); ++index) + spells.push_back(const_cast(&mSpells.getRecord(index).get())); + } + }; + + unsigned short autoCalculateMana(const AutoCalc::StatsBase& stats) + { + return stats.getBaseAttribute(ESM::Attribute::Intelligence) * 2; + } + + unsigned short autoCalculateFatigue(const AutoCalc::StatsBase& stats) + { + return stats.getBaseAttribute(ESM::Attribute::Strength) + + stats.getBaseAttribute(ESM::Attribute::Willpower) + + stats.getBaseAttribute(ESM::Attribute::Agility) + + stats.getBaseAttribute(ESM::Attribute::Endurance); + } +} + +CSMWorld::NpcAutoCalc::NpcAutoCalc (const Data& data, + const IdTable *gmsts, const IdTable *skills, const IdTable *classes, const IdTree *races, const IdTree *objects) +: mData(data), mSkillModel(skills), mClassModel(classes), mRaceModel(races) +{ + // for autocalc updates when gmst/race/class/skils tables change + connect (gmsts, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (gmstDataChanged (const QModelIndex&, const QModelIndex&))); + connect (mSkillModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (skillDataChanged (const QModelIndex&, const QModelIndex&))); + connect (mClassModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (classDataChanged (const QModelIndex&, const QModelIndex&))); + connect (mRaceModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (raceDataChanged (const QModelIndex&, const QModelIndex&))); + connect (objects, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (npcDataChanged (const QModelIndex&, const QModelIndex&))); + connect (this, SIGNAL (updateNpcAutocalc (int, const std::string&)), + objects, SLOT (updateNpcAutocalc (int, const std::string&))); + + //connect (this, SIGNAL (cacheNpcStats (const std::string&, NpcStats*)), + //this, SLOT (cacheNpcStatsEvent (const std::string&, NpcStats*))); +} + +CSMWorld::NpcAutoCalc::~NpcAutoCalc() +{ + clearNpcStatsCache(); +} + +void CSMWorld::NpcAutoCalc::skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // mData.mAttribute (affects attributes skill bonus autocalc) + // mData.mSpecialization (affects skills autocalc) + int attributeColumn = mSkillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute); + int specialisationColumn = mSkillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation); + + if ((topLeft.column() <= attributeColumn && attributeColumn <= bottomRight.column()) + || (topLeft.column() <= specialisationColumn && specialisationColumn <= bottomRight.column())) + { + clearNpcStatsCache(); + + std::string empty; + emit updateNpcAutocalc(0/*all*/, empty); + } +} + +void CSMWorld::NpcAutoCalc::classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // update autocalculated attributes/skills of every NPC with matching class + // - mData.mAttribute[2] + // - mData.mSkills[5][2] + // - mData.mSpecialization + int attribute1Column = mClassModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute1); // +1 + int majorSkill1Column = mClassModel->findColumnIndex(CSMWorld::Columns::ColumnId_MajorSkill1); // +4 + int minorSkill1Column = mClassModel->findColumnIndex(CSMWorld::Columns::ColumnId_MinorSkill1); // +4 + int specialisationColumn = mClassModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation); + + if ((topLeft.column() > attribute1Column+1 || attribute1Column > bottomRight.column()) + && (topLeft.column() > majorSkill1Column+4 || majorSkill1Column > bottomRight.column()) + && (topLeft.column() > minorSkill1Column+4 || minorSkill1Column > bottomRight.column()) + && (topLeft.column() > specialisationColumn || specialisationColumn > bottomRight.column())) + { + return; + } + + // get the affected class + int idColumn = mClassModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + for (int classRow = topLeft.row(); classRow <= bottomRight.row(); ++classRow) + { + clearNpcStatsCache(); + + std::string classId = + mClassModel->data(mClassModel->index(classRow, idColumn)).toString().toUtf8().constData(); + emit updateNpcAutocalc(1/*class*/, classId); + } +} + +void CSMWorld::NpcAutoCalc::raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // affects racial bonus attributes & skills + // - mData.mAttributeValues[] + // - mData.mBonus[].mBonus + // - mPowers.mList[] + int attrColumn = mRaceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceAttributes); + int bonusColumn = mRaceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceSkillBonus); + int powersColumn = mRaceModel->findColumnIndex(CSMWorld::Columns::ColumnId_PowerList); + + bool match = false; + int raceRow = topLeft.row(); + int raceEnd = bottomRight.row(); + if (topLeft.parent().isValid() && bottomRight.parent().isValid()) + { + if ((topLeft.parent().column() <= attrColumn && attrColumn <= bottomRight.parent().column()) + || (topLeft.parent().column() <= bonusColumn && bonusColumn <= bottomRight.parent().column()) + || (topLeft.parent().column() <= powersColumn && powersColumn <= bottomRight.parent().column())) + { + match = true; // TODO: check for specific nested column? + raceRow = topLeft.parent().row(); + raceEnd = bottomRight.parent().row(); + } + } + else + { + if ((topLeft.column() <= attrColumn && attrColumn <= bottomRight.column()) + || (topLeft.column() <= bonusColumn && bonusColumn <= bottomRight.column()) + || (topLeft.column() <= powersColumn && powersColumn <= bottomRight.column())) + { + match = true; // maybe the whole table changed + } + } + + if (!match) + return; + + // update autocalculated attributes/skills of every NPC with matching race + int idColumn = mRaceModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + for (; raceRow <= raceEnd; ++raceRow) + { + clearNpcStatsCache(); + + std::string raceId = + mRaceModel->data(mRaceModel->index(raceRow, idColumn)).toString().toUtf8().constData(); + emit updateNpcAutocalc(2/*race*/, raceId); + } +} + +void CSMWorld::NpcAutoCalc::npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // TODO: for now always recalculate + clearNpcStatsCache(); + + // TODO: check if below signal slows things down + std::string empty; + emit updateNpcAutocalc(0/*all*/, empty); +} + +void CSMWorld::NpcAutoCalc::gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + static const QStringList gmsts(QStringList()<< "fNPCbaseMagickaMult" << "fAutoSpellChance" + << "fEffectCostMult" << "iAutoSpellAlterationMax" << "iAutoSpellConjurationMax" + << "iAutoSpellDestructionMax" << "iAutoSpellIllusionMax" << "iAutoSpellMysticismMax" + << "iAutoSpellRestorationMax" << "iAutoSpellTimesCanCast" << "iAutoSpellAttSkillMin"); + + bool match = false; + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) + { + if (gmsts.contains(mData.getGmsts().getRecord(row).get().mId.c_str())) + { + match = true; + break; + } + } + + if (!match) + return; + + clearNpcStatsCache(); + + std::string empty; + emit updateNpcAutocalc(0/*all*/, empty); +} + +void CSMWorld::NpcAutoCalc::clearNpcStatsCache () +{ + for (std::map::iterator it (mNpcStatCache.begin()); + it != mNpcStatCache.end(); ++it) + delete it->second; + + mNpcStatCache.clear(); +} + +CSMWorld::NpcStats* CSMWorld::NpcAutoCalc::npcAutoCalculate(const ESM::NPC& npc) const +{ + CSMWorld::NpcStats *cachedStats = getCachedNpcData (npc.mId); + if (cachedStats) + return cachedStats; + + int raceIndex = mData.getRaces().searchId(npc.mRace); + int classIndex = mData.getClasses().searchId(npc.mClass); + // this can happen when creating a new game from scratch + if (raceIndex == -1 || classIndex == -1) + return 0; + + const ESM::Race *race = &mData.getRaces().getRecord(raceIndex).get(); + const ESM::Class *class_ = &mData.getClasses().getRecord(classIndex).get(); + + bool autoCalc = npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; + short level = npc.mNpdt52.mLevel; + if (autoCalc) + level = npc.mNpdt12.mLevel; + + std::auto_ptr stats (new CSMWorld::NpcStats()); + + CSStore store(mData.getGmsts(), mData.getSkills(), mData.getMagicEffects(), static_cast&>(mData.getSpells())); + + if (autoCalc) + { + AutoCalc::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store); + + stats->setHealth(autoCalculateHealth(level, class_, *stats)); + stats->setMana(autoCalculateMana(*stats)); + stats->setFatigue(autoCalculateFatigue(*stats)); + + AutoCalc::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store); + + AutoCalc::autoCalculateSpells(race, *stats, &store); + } + else + { + for (std::vector::const_iterator it = npc.mSpells.mList.begin(); + it != npc.mSpells.mList.end(); ++it) + { + stats->addSpell(*it); + } + } + + // update spell info + const std::vector &racePowers = race->mPowers.mList; + for (unsigned int i = 0; i < racePowers.size(); ++i) + { + int type = -1; + int spellIndex = mData.getSpells().searchId(racePowers[i]); + if (spellIndex != -1) + type = mData.getSpells().getRecord(spellIndex).get().mData.mType; + stats->addPowers(racePowers[i], type); + } + // cost/chance + int skills[ESM::Skill::Length]; + if (autoCalc) + for (int i = 0; i< ESM::Skill::Length; ++i) + skills[i] = stats->getBaseSkill(i); + else + for (int i = 0; i< ESM::Skill::Length; ++i) + skills[i] = npc.mNpdt52.mSkills[i]; + + int attributes[ESM::Attribute::Length]; + if (autoCalc) + for (int i = 0; i< ESM::Attribute::Length; ++i) + attributes[i] = stats->getBaseAttribute(i); + else + { + attributes[ESM::Attribute::Strength] = npc.mNpdt52.mStrength; + attributes[ESM::Attribute::Willpower] = npc.mNpdt52.mWillpower; + attributes[ESM::Attribute::Agility] = npc.mNpdt52.mAgility; + attributes[ESM::Attribute::Speed] = npc.mNpdt52.mSpeed; + attributes[ESM::Attribute::Endurance] = npc.mNpdt52.mEndurance; + attributes[ESM::Attribute::Personality] = npc.mNpdt52.mPersonality; + attributes[ESM::Attribute::Luck] = npc.mNpdt52.mLuck; + } + + const std::vector& spells = stats->spells(); + for (std::vector::const_iterator it = spells.begin(); it != spells.end(); ++it) + { + int cost = -1; + int spellIndex = mData.getSpells().searchId((*it).mName); + const ESM::Spell* spell = 0; + if (spellIndex != -1) + { + spell = &mData.getSpells().getRecord(spellIndex).get(); + cost = spell->mData.mCost; + + int school; + float skillTerm; + AutoCalc::calcWeakestSchool(spell, skills, school, skillTerm, &store); + float chance = calcAutoCastChance(spell, skills, attributes, school, &store); + + stats->addCostAndChance((*it).mName, cost, (int)ceil(chance)); // percent + } + } + + if (stats.get() == 0) + return 0; + + CSMWorld::NpcStats *result = stats.release(); + //emit cacheNpcStats (npc.mId, result); + mNpcStatCache[npc.mId] = result; + return result; +} + +//void CSMWorld::NpcAutoCalc::cacheNpcStatsEvent (const std::string& id, CSMWorld::NpcStats *stats) +//{ + //mNpcStatCache[id] = stats; +//} + +CSMWorld::NpcStats* CSMWorld::NpcAutoCalc::getCachedNpcData (const std::string& id) const +{ + std::map::const_iterator it = mNpcStatCache.find(id); + if (it != mNpcStatCache.end()) + return it->second; + else + return 0; +} diff --git a/apps/opencs/model/world/npcautocalc.hpp b/apps/opencs/model/world/npcautocalc.hpp new file mode 100644 index 000000000..ceb604d35 --- /dev/null +++ b/apps/opencs/model/world/npcautocalc.hpp @@ -0,0 +1,75 @@ +#ifndef CSM_WORLD_NPCAUTOCALC_H +#define CSM_WORLD_NPCAUTOCALC_H + +#include +#include + +#include +#include + +namespace ESM +{ + struct NPC; +} + +namespace CSMWorld +{ + class Data; + class NpcStats; + class IdTable; + class IdTree; + + class NpcAutoCalc : public QObject + { + Q_OBJECT + + const Data& mData; + const IdTable *mSkillModel; + const IdTable *mClassModel; + const IdTree *mRaceModel; + mutable std::map mNpcStatCache; + + public: + + NpcAutoCalc (const Data& data, const IdTable *gmsts, const IdTable *skills, const IdTable *classes, + const IdTree *races, const IdTree *objects); + + ~NpcAutoCalc (); + + NpcStats* npcAutoCalculate (const ESM::NPC& npc) const; + + private: + + // not implemented + NpcAutoCalc (const NpcAutoCalc&); + NpcAutoCalc& operator= (const NpcAutoCalc&); + + NpcStats* getCachedNpcData (const std::string& id) const; + + void clearNpcStatsCache (); + + signals: + + // refresh NPC dialogue subviews via object table model + void updateNpcAutocalc (int type, const std::string& id); + + //void cacheNpcStats (const std::string& id, NpcStats *stats) const; + + private slots: + + // for autocalc updates when gmst/race/class/skils tables change + void gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + //void cacheNpcStatsEvent (const std::string& id, NpcStats *stats); + }; +} + +#endif // CSM_WORLD_NPCAUTOCALC_H diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index a268cb5f0..1683fd000 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -12,6 +12,7 @@ #include "usertype.hpp" #include "idtree.hpp" #include "npcstats.hpp" +#include "npcautocalc.hpp" CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) : InventoryColumns (columns) {} @@ -777,7 +778,7 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d { if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc); if (!stats) { record.setModified (npc); @@ -817,7 +818,7 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d { npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc - mData.npcAutoCalculate(npc); + mData.getNpcAutoCalc().npcAutoCalculate(npc); } } } @@ -888,7 +889,7 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * else if (subColIndex == 1) if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc); if (!stats) return QVariant(); @@ -1020,7 +1021,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu { if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc); if (!stats) return QVariant(); @@ -1108,7 +1109,7 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column if (autoCalc) { - CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc); switch (subColIndex) { @@ -1732,7 +1733,7 @@ QVariant NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *co const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - CSMWorld::NpcStats *stats = mData.npcAutoCalculate(record.get()); + CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(record.get()); if (!stats) return QVariant(); @@ -1766,7 +1767,7 @@ int NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *col const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - CSMWorld::NpcStats *stats = mData.npcAutoCalculate(record.get()); + CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(record.get()); if (!stats) return 0; From a38881e4cf26782c4277d075340c8456e4212a64 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 8 Nov 2015 09:45:12 +1100 Subject: [PATCH 253/441] Further clean up of moving out NpcAutoCalc from Data. --- apps/opencs/model/world/data.cpp | 1 - apps/opencs/model/world/data.hpp | 3 --- 2 files changed, 4 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 84cb505c0..c87f590a8 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -18,7 +18,6 @@ #include "resourcesmanager.hpp" #include "resourcetable.hpp" #include "nestedcoladapterimp.hpp" -#include "npcstats.hpp" #include "npcautocalc.hpp" void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update) diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 3d3996a1d..a3ccb28be 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -61,7 +61,6 @@ namespace CSMWorld { class ResourcesManager; class Resources; - class NpcStats; class NpcAutoCalc; class Data : public QObject @@ -127,8 +126,6 @@ namespace CSMWorld const Data& self (); - void clearNpcStatsCache (); - public: Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager); From f68e423c331f4bcb784f69f901eae6e14d2b0fd0 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 22 Nov 2015 18:43:44 +1100 Subject: [PATCH 254/441] Add missing Ogre headers. --- apps/opencs/view/render/cell.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index f81b0708f..44dccb67d 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include From cc984497d7d8e524d872613ed6416d1af175edf8 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 22 Nov 2015 18:49:58 +1100 Subject: [PATCH 255/441] Fix warning C4838 (conversion from 'double' to 'const float' requires a narrowing conversion) --- apps/opencs/model/doc/document.cpp | 516 ++++++++++++++--------------- 1 file changed, 258 insertions(+), 258 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 925eef659..18dc9af4a 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -278,264 +278,264 @@ void CSMDoc::Document::addGmsts() static const float gmstFloatsValues[] = { - 0.3, // fAIFleeFleeMult - 7.0, // fAIFleeHealthMult - 3.0, // fAIMagicSpellMult - 1.0, // fAIMeleeArmorMult - 1.0, // fAIMeleeSummWeaponMult - 2.0, // fAIMeleeWeaponMult - 5.0, // fAIRangeMagicSpellMult - 5.0, // fAIRangeMeleeWeaponMult - 2000.0, // fAlarmRadius - 1.0, // fAthleticsRunBonus - 40.0, // fAudioDefaultMaxDistance - 5.0, // fAudioDefaultMinDistance - 50.0, // fAudioMaxDistanceMult - 20.0, // fAudioMinDistanceMult - 60.0, // fAudioVoiceDefaultMaxDistance - 10.0, // fAudioVoiceDefaultMinDistance - 50.0, // fAutoPCSpellChance - 80.0, // fAutoSpellChance - 50.0, // fBargainOfferBase - -4.0, // fBargainOfferMulti - 24.0, // fBarterGoldResetDelay - 1.75, // fBaseRunMultiplier - 1.25, // fBlockStillBonus - 150.0, // fBribe1000Mod - 75.0, // fBribe100Mod - 35.0, // fBribe10Mod - 60.0, // fCombatAngleXY - 60.0, // fCombatAngleZ - 0.25, // fCombatArmorMinMult - -90.0, // fCombatBlockLeftAngle - 30.0, // fCombatBlockRightAngle - 4.0, // fCombatCriticalStrikeMult - 0.1, // fCombatDelayCreature - 0.1, // fCombatDelayNPC - 128.0, // fCombatDistance - 0.3, // fCombatDistanceWerewolfMod - 30.0, // fCombatForceSideAngle - 0.2, // fCombatInvisoMult - 1.5, // fCombatKODamageMult - 45.0, // fCombatTorsoSideAngle - 0.3, // fCombatTorsoStartPercent - 0.8, // fCombatTorsoStopPercent - 15.0, // fConstantEffectMult - 72.0, // fCorpseClearDelay - 72.0, // fCorpseRespawnDelay - 0.5, // fCrimeGoldDiscountMult - 0.9, // fCrimeGoldTurnInMult - 1.0, // fCrimeStealing - 0.5, // fDamageStrengthBase - 0.1, // fDamageStrengthMult - 5.0, // fDifficultyMult - 2.5, // fDiseaseXferChance - -10.0, // fDispAttacking - -1.0, // fDispBargainFailMod - 1.0, // fDispBargainSuccessMod - 0.0, // fDispCrimeMod - -10.0, // fDispDiseaseMod - 3.0, // fDispFactionMod - 1.0, // fDispFactionRankBase - 0.5, // fDispFactionRankMult - 1.0, // fDispositionMod - 50.0, // fDispPersonalityBase - 0.5, // fDispPersonalityMult - -25.0, // fDispPickPocketMod - 5.0, // fDispRaceMod - -0.5, // fDispStealing - -5.0, // fDispWeaponDrawn - 0.5, // fEffectCostMult - 0.1, // fElementalShieldMult - 3.0, // fEnchantmentChanceMult - 0.5, // fEnchantmentConstantChanceMult - 100.0, // fEnchantmentConstantDurationMult - 0.1, // fEnchantmentMult - 1000.0, // fEnchantmentValueMult - 0.3, // fEncumberedMoveEffect - 5.0, // fEncumbranceStrMult - 0.04, // fEndFatigueMult - 0.25, // fFallAcroBase - 0.01, // fFallAcroMult - 400.0, // fFallDamageDistanceMin - 0.0, // fFallDistanceBase - 0.07, // fFallDistanceMult - 2.0, // fFatigueAttackBase - 0.0, // fFatigueAttackMult - 1.25, // fFatigueBase - 4.0, // fFatigueBlockBase - 0.0, // fFatigueBlockMult - 5.0, // fFatigueJumpBase - 0.0, // fFatigueJumpMult - 0.5, // fFatigueMult - 2.5, // fFatigueReturnBase - 0.02, // fFatigueReturnMult - 5.0, // fFatigueRunBase - 2.0, // fFatigueRunMult - 1.5, // fFatigueSneakBase - 1.5, // fFatigueSneakMult - 0.0, // fFatigueSpellBase - 0.0, // fFatigueSpellCostMult - 0.0, // fFatigueSpellMult - 7.0, // fFatigueSwimRunBase - 0.0, // fFatigueSwimRunMult - 2.5, // fFatigueSwimWalkBase - 0.0, // fFatigueSwimWalkMult - 0.2, // fFightDispMult - 0.005, // fFightDistanceMultiplier - 50.0, // fFightStealing - 3000.0, // fFleeDistance - 512.0, // fGreetDistanceReset - 0.1, // fHandtoHandHealthPer - 1.0, // fHandToHandReach - 0.5, // fHoldBreathEndMult - 20.0, // fHoldBreathTime - 0.75, // fIdleChanceMultiplier - 1.0, // fIngredientMult - 0.5, // fInteriorHeadTrackMult - 128.0, // fJumpAcrobaticsBase - 4.0, // fJumpAcroMultiplier - 0.5, // fJumpEncumbranceBase - 1.0, // fJumpEncumbranceMultiplier - 0.5, // fJumpMoveBase - 0.5, // fJumpMoveMult - 1.0, // fJumpRunMultiplier - 0.5, // fKnockDownMult - 5.0, // fLevelMod - 0.1, // fLevelUpHealthEndMult - 0.6, // fLightMaxMod - 10.0, // fLuckMod - 10.0, // fMagesGuildTravel - 1.5, // fMagicCreatureCastDelay - 0.0167, // fMagicDetectRefreshRate - 1.0, // fMagicItemConstantMult - 1.0, // fMagicItemCostMult - 1.0, // fMagicItemOnceMult - 1.0, // fMagicItemPriceMult - 0.05, // fMagicItemRechargePerSecond - 1.0, // fMagicItemStrikeMult - 1.0, // fMagicItemUsedMult - 3.0, // fMagicStartIconBlink - 0.5, // fMagicSunBlockedMult - 0.75, // fMajorSkillBonus - 300.0, // fMaxFlySpeed - 0.5, // fMaxHandToHandMult - 400.0, // fMaxHeadTrackDistance - 200.0, // fMaxWalkSpeed - 300.0, // fMaxWalkSpeedCreature - 0.9, // fMedMaxMod - 0.1, // fMessageTimePerChar - 5.0, // fMinFlySpeed - 0.1, // fMinHandToHandMult - 1.0, // fMinorSkillBonus - 100.0, // fMinWalkSpeed - 5.0, // fMinWalkSpeedCreature - 1.25, // fMiscSkillBonus - 2.0, // fNPCbaseMagickaMult - 0.5, // fNPCHealthBarFade - 3.0, // fNPCHealthBarTime - 1.0, // fPCbaseMagickaMult - 0.3, // fPerDieRollMult - 5.0, // fPersonalityMod - 1.0, // fPerTempMult - -1.0, // fPickLockMult - 0.3, // fPickPocketMod - 20.0, // fPotionMinUsefulDuration - 0.5, // fPotionStrengthMult - 0.5, // fPotionT1DurMult - 1.5, // fPotionT1MagMult - 20.0, // fPotionT4BaseStrengthMult - 12.0, // fPotionT4EquipStrengthMult - 3000.0, // fProjectileMaxSpeed - 400.0, // fProjectileMinSpeed - 25.0, // fProjectileThrownStoreChance - 3.0, // fRepairAmountMult - 1.0, // fRepairMult - 1.0, // fReputationMod - 0.15, // fRestMagicMult - 0.0, // fSeriousWoundMult - 0.25, // fSleepRandMod - 0.3, // fSleepRestMod - -1.0, // fSneakBootMult - 0.5, // fSneakDistanceBase - 0.002, // fSneakDistanceMultiplier - 0.5, // fSneakNoViewMult - 1.0, // fSneakSkillMult - 0.75, // fSneakSpeedMultiplier - 1.0, // fSneakUseDelay - 500.0, // fSneakUseDist - 1.5, // fSneakViewMult - 3.0, // fSoulGemMult - 0.8, // fSpecialSkillBonus - 7.0, // fSpellMakingValueMult - 2.0, // fSpellPriceMult - 10.0, // fSpellValueMult - 0.25, // fStromWalkMult - 0.7, // fStromWindSpeed - 3.0, // fSuffocationDamage - 0.9, // fSwimHeightScale - 0.1, // fSwimRunAthleticsMult - 0.5, // fSwimRunBase - 0.02, // fSwimWalkAthleticsMult - 0.5, // fSwimWalkBase - 1.0, // fSwingBlockBase - 1.0, // fSwingBlockMult - 1000.0, // fTargetSpellMaxSpeed - 1000.0, // fThrownWeaponMaxSpeed - 300.0, // fThrownWeaponMinSpeed - 0.0, // fTrapCostMult - 4000.0, // fTravelMult - 16000.0,// fTravelTimeMult - 0.1, // fUnarmoredBase1 - 0.065, // fUnarmoredBase2 - 30.0, // fVanityDelay - 10.0, // fVoiceIdleOdds - 0.0, // fWaterReflectUpdateAlways - 10.0, // fWaterReflectUpdateSeldom - 0.1, // fWeaponDamageMult - 1.0, // fWeaponFatigueBlockMult - 0.25, // fWeaponFatigueMult - 150.0, // fWereWolfAcrobatics - 150.0, // fWereWolfAgility - 1.0, // fWereWolfAlchemy - 1.0, // fWereWolfAlteration - 1.0, // fWereWolfArmorer - 150.0, // fWereWolfAthletics - 1.0, // fWereWolfAxe - 1.0, // fWereWolfBlock - 1.0, // fWereWolfBluntWeapon - 1.0, // fWereWolfConjuration - 1.0, // fWereWolfDestruction - 1.0, // fWereWolfEnchant - 150.0, // fWereWolfEndurance - 400.0, // fWereWolfFatigue - 100.0, // fWereWolfHandtoHand - 2.0, // fWereWolfHealth - 1.0, // fWereWolfHeavyArmor - 1.0, // fWereWolfIllusion - 1.0, // fWereWolfIntellegence - 1.0, // fWereWolfLightArmor - 1.0, // fWereWolfLongBlade - 1.0, // fWereWolfLuck - 100.0, // fWereWolfMagicka - 1.0, // fWereWolfMarksman - 1.0, // fWereWolfMediumArmor - 1.0, // fWereWolfMerchantile - 1.0, // fWereWolfMysticism - 1.0, // fWereWolfPersonality - 1.0, // fWereWolfRestoration - 1.5, // fWereWolfRunMult - 1.0, // fWereWolfSecurity - 1.0, // fWereWolfShortBlade - 1.5, // fWereWolfSilverWeaponDamageMult - 1.0, // fWereWolfSneak - 1.0, // fWereWolfSpear - 1.0, // fWereWolfSpeechcraft - 150.0, // fWereWolfSpeed - 150.0, // fWereWolfStrength - 100.0, // fWereWolfUnarmored - 1.0, // fWereWolfWillPower - 15.0, // fWortChanceValue + 0.3f, // fAIFleeFleeMult + 7.0f, // fAIFleeHealthMult + 3.0f, // fAIMagicSpellMult + 1.0f, // fAIMeleeArmorMult + 1.0f, // fAIMeleeSummWeaponMult + 2.0f, // fAIMeleeWeaponMult + 5.0f, // fAIRangeMagicSpellMult + 5.0f, // fAIRangeMeleeWeaponMult + 2000.0f, // fAlarmRadius + 1.0f, // fAthleticsRunBonus + 40.0f, // fAudioDefaultMaxDistance + 5.0f, // fAudioDefaultMinDistance + 50.0f, // fAudioMaxDistanceMult + 20.0f, // fAudioMinDistanceMult + 60.0f, // fAudioVoiceDefaultMaxDistance + 10.0f, // fAudioVoiceDefaultMinDistance + 50.0f, // fAutoPCSpellChance + 80.0f, // fAutoSpellChance + 50.0f, // fBargainOfferBase + -4.0f, // fBargainOfferMulti + 24.0f, // fBarterGoldResetDelay + 1.75f, // fBaseRunMultiplier + 1.25f, // fBlockStillBonus + 150.0f, // fBribe1000Mod + 75.0f, // fBribe100Mod + 35.0f, // fBribe10Mod + 60.0f, // fCombatAngleXY + 60.0f, // fCombatAngleZ + 0.25f, // fCombatArmorMinMult + -90.0f, // fCombatBlockLeftAngle + 30.0f, // fCombatBlockRightAngle + 4.0f, // fCombatCriticalStrikeMult + 0.1f, // fCombatDelayCreature + 0.1f, // fCombatDelayNPC + 128.0f, // fCombatDistance + 0.3f, // fCombatDistanceWerewolfMod + 30.0f, // fCombatForceSideAngle + 0.2f, // fCombatInvisoMult + 1.5f, // fCombatKODamageMult + 45.0f, // fCombatTorsoSideAngle + 0.3f, // fCombatTorsoStartPercent + 0.8f, // fCombatTorsoStopPercent + 15.0f, // fConstantEffectMult + 72.0f, // fCorpseClearDelay + 72.0f, // fCorpseRespawnDelay + 0.5f, // fCrimeGoldDiscountMult + 0.9f, // fCrimeGoldTurnInMult + 1.0f, // fCrimeStealing + 0.5f, // fDamageStrengthBase + 0.1f, // fDamageStrengthMult + 5.0f, // fDifficultyMult + 2.5f, // fDiseaseXferChance + -10.0f, // fDispAttacking + -1.0f, // fDispBargainFailMod + 1.0f, // fDispBargainSuccessMod + 0.0f, // fDispCrimeMod + -10.0f, // fDispDiseaseMod + 3.0f, // fDispFactionMod + 1.0f, // fDispFactionRankBase + 0.5f, // fDispFactionRankMult + 1.0f, // fDispositionMod + 50.0f, // fDispPersonalityBase + 0.5f, // fDispPersonalityMult + -25.0f, // fDispPickPocketMod + 5.0f, // fDispRaceMod + -0.5f, // fDispStealing + -5.0f, // fDispWeaponDrawn + 0.5f, // fEffectCostMult + 0.1f, // fElementalShieldMult + 3.0f, // fEnchantmentChanceMult + 0.5f, // fEnchantmentConstantChanceMult + 100.0f, // fEnchantmentConstantDurationMult + 0.1f, // fEnchantmentMult + 1000.0f, // fEnchantmentValueMult + 0.3f, // fEncumberedMoveEffect + 5.0f, // fEncumbranceStrMult + 0.04f, // fEndFatigueMult + 0.25f, // fFallAcroBase + 0.01f, // fFallAcroMult + 400.0f, // fFallDamageDistanceMin + 0.0f, // fFallDistanceBase + 0.07f, // fFallDistanceMult + 2.0f, // fFatigueAttackBase + 0.0f, // fFatigueAttackMult + 1.25f, // fFatigueBase + 4.0f, // fFatigueBlockBase + 0.0f, // fFatigueBlockMult + 5.0f, // fFatigueJumpBase + 0.0f, // fFatigueJumpMult + 0.5f, // fFatigueMult + 2.5f, // fFatigueReturnBase + 0.02f, // fFatigueReturnMult + 5.0f, // fFatigueRunBase + 2.0f, // fFatigueRunMult + 1.5f, // fFatigueSneakBase + 1.5f, // fFatigueSneakMult + 0.0f, // fFatigueSpellBase + 0.0f, // fFatigueSpellCostMult + 0.0f, // fFatigueSpellMult + 7.0f, // fFatigueSwimRunBase + 0.0f, // fFatigueSwimRunMult + 2.5f, // fFatigueSwimWalkBase + 0.0f, // fFatigueSwimWalkMult + 0.2f, // fFightDispMult + 0.005f, // fFightDistanceMultiplier + 50.0f, // fFightStealing + 3000.0f, // fFleeDistance + 512.0f, // fGreetDistanceReset + 0.1f, // fHandtoHandHealthPer + 1.0f, // fHandToHandReach + 0.5f, // fHoldBreathEndMult + 20.0f, // fHoldBreathTime + 0.75f, // fIdleChanceMultiplier + 1.0f, // fIngredientMult + 0.5f, // fInteriorHeadTrackMult + 128.0f, // fJumpAcrobaticsBase + 4.0f, // fJumpAcroMultiplier + 0.5f, // fJumpEncumbranceBase + 1.0f, // fJumpEncumbranceMultiplier + 0.5f, // fJumpMoveBase + 0.5f, // fJumpMoveMult + 1.0f, // fJumpRunMultiplier + 0.5f, // fKnockDownMult + 5.0f, // fLevelMod + 0.1f, // fLevelUpHealthEndMult + 0.6f, // fLightMaxMod + 10.0f, // fLuckMod + 10.0f, // fMagesGuildTravel + 1.5f, // fMagicCreatureCastDelay + 0.0167f, // fMagicDetectRefreshRate + 1.0f, // fMagicItemConstantMult + 1.0f, // fMagicItemCostMult + 1.0f, // fMagicItemOnceMult + 1.0f, // fMagicItemPriceMult + 0.05f, // fMagicItemRechargePerSecond + 1.0f, // fMagicItemStrikeMult + 1.0f, // fMagicItemUsedMult + 3.0f, // fMagicStartIconBlink + 0.5f, // fMagicSunBlockedMult + 0.75f, // fMajorSkillBonus + 300.0f, // fMaxFlySpeed + 0.5f, // fMaxHandToHandMult + 400.0f, // fMaxHeadTrackDistance + 200.0f, // fMaxWalkSpeed + 300.0f, // fMaxWalkSpeedCreature + 0.9f, // fMedMaxMod + 0.1f, // fMessageTimePerChar + 5.0f, // fMinFlySpeed + 0.1f, // fMinHandToHandMult + 1.0f, // fMinorSkillBonus + 100.0f, // fMinWalkSpeed + 5.0f, // fMinWalkSpeedCreature + 1.25f, // fMiscSkillBonus + 2.0f, // fNPCbaseMagickaMult + 0.5f, // fNPCHealthBarFade + 3.0f, // fNPCHealthBarTime + 1.0f, // fPCbaseMagickaMult + 0.3f, // fPerDieRollMult + 5.0f, // fPersonalityMod + 1.0f, // fPerTempMult + -1.0f, // fPickLockMult + 0.3f, // fPickPocketMod + 20.0f, // fPotionMinUsefulDuration + 0.5f, // fPotionStrengthMult + 0.5f, // fPotionT1DurMult + 1.5f, // fPotionT1MagMult + 20.0f, // fPotionT4BaseStrengthMult + 12.0f, // fPotionT4EquipStrengthMult + 3000.0f, // fProjectileMaxSpeed + 400.0f, // fProjectileMinSpeed + 25.0f, // fProjectileThrownStoreChance + 3.0f, // fRepairAmountMult + 1.0f, // fRepairMult + 1.0f, // fReputationMod + 0.15f, // fRestMagicMult + 0.0f, // fSeriousWoundMult + 0.25f, // fSleepRandMod + 0.3f, // fSleepRestMod + -1.0f, // fSneakBootMult + 0.5f, // fSneakDistanceBase + 0.002f, // fSneakDistanceMultiplier + 0.5f, // fSneakNoViewMult + 1.0f, // fSneakSkillMult + 0.75f, // fSneakSpeedMultiplier + 1.0f, // fSneakUseDelay + 500.0f, // fSneakUseDist + 1.5f, // fSneakViewMult + 3.0f, // fSoulGemMult + 0.8f, // fSpecialSkillBonus + 7.0f, // fSpellMakingValueMult + 2.0f, // fSpellPriceMult + 10.0f, // fSpellValueMult + 0.25f, // fStromWalkMult + 0.7f, // fStromWindSpeed + 3.0f, // fSuffocationDamage + 0.9f, // fSwimHeightScale + 0.1f, // fSwimRunAthleticsMult + 0.5f, // fSwimRunBase + 0.02f, // fSwimWalkAthleticsMult + 0.5f, // fSwimWalkBase + 1.0f, // fSwingBlockBase + 1.0f, // fSwingBlockMult + 1000.0f, // fTargetSpellMaxSpeed + 1000.0f, // fThrownWeaponMaxSpeed + 300.0f, // fThrownWeaponMinSpeed + 0.0f, // fTrapCostMult + 4000.0f, // fTravelMult + 16000.0f,// fTravelTimeMult + 0.1f, // fUnarmoredBase1 + 0.065f, // fUnarmoredBase2 + 30.0f, // fVanityDelay + 10.0f, // fVoiceIdleOdds + 0.0f, // fWaterReflectUpdateAlways + 10.0f, // fWaterReflectUpdateSeldom + 0.1f, // fWeaponDamageMult + 1.0f, // fWeaponFatigueBlockMult + 0.25f, // fWeaponFatigueMult + 150.0f, // fWereWolfAcrobatics + 150.0f, // fWereWolfAgility + 1.0f, // fWereWolfAlchemy + 1.0f, // fWereWolfAlteration + 1.0f, // fWereWolfArmorer + 150.0f, // fWereWolfAthletics + 1.0f, // fWereWolfAxe + 1.0f, // fWereWolfBlock + 1.0f, // fWereWolfBluntWeapon + 1.0f, // fWereWolfConjuration + 1.0f, // fWereWolfDestruction + 1.0f, // fWereWolfEnchant + 150.0f, // fWereWolfEndurance + 400.0f, // fWereWolfFatigue + 100.0f, // fWereWolfHandtoHand + 2.0f, // fWereWolfHealth + 1.0f, // fWereWolfHeavyArmor + 1.0f, // fWereWolfIllusion + 1.0f, // fWereWolfIntellegence + 1.0f, // fWereWolfLightArmor + 1.0f, // fWereWolfLongBlade + 1.0f, // fWereWolfLuck + 100.0f, // fWereWolfMagicka + 1.0f, // fWereWolfMarksman + 1.0f, // fWereWolfMediumArmor + 1.0f, // fWereWolfMerchantile + 1.0f, // fWereWolfMysticism + 1.0f, // fWereWolfPersonality + 1.0f, // fWereWolfRestoration + 1.5f, // fWereWolfRunMult + 1.0f, // fWereWolfSecurity + 1.0f, // fWereWolfShortBlade + 1.5f, // fWereWolfSilverWeaponDamageMult + 1.0f, // fWereWolfSneak + 1.0f, // fWereWolfSpear + 1.0f, // fWereWolfSpeechcraft + 150.0f, // fWereWolfSpeed + 150.0f, // fWereWolfStrength + 100.0f, // fWereWolfUnarmored + 1.0f, // fWereWolfWillPower + 15.0f, // fWortChanceValue }; static const char *gmstIntegers[] = From d70064efe427fbd94e1b22d52b31b47b0a31677e Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 22 Nov 2015 19:15:06 +1100 Subject: [PATCH 256/441] Fix warning C4456 (declaration hides previous local declaration) --- apps/opencs/model/doc/loader.cpp | 8 ++++---- apps/opencs/model/doc/savingstages.cpp | 8 ++++---- apps/opencs/model/filter/parser.cpp | 6 +++--- apps/opencs/model/tools/mergestages.cpp | 6 +++--- apps/opencs/model/world/idtable.cpp | 4 ++-- apps/opencs/model/world/infocollection.cpp | 18 +++++++++--------- apps/opencs/model/world/refcollection.cpp | 6 +++--- apps/opencs/model/world/refidcollection.cpp | 4 ++-- apps/opencs/model/world/resources.cpp | 16 ++++++++-------- apps/opencs/view/render/textoverlay.cpp | 2 +- apps/opencs/view/world/dialoguesubview.cpp | 8 ++++---- 11 files changed, 43 insertions(+), 43 deletions(-) diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index cb3ff2cd0..b522f89f2 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -64,11 +64,11 @@ void CSMDoc::Loader::load() CSMWorld::UniversalId log (CSMWorld::UniversalId::Type_LoadErrorLog, 0); { // silence a g++ warning - for (CSMDoc::Messages::Iterator iter (messages.begin()); - iter!=messages.end(); ++iter) + for (CSMDoc::Messages::Iterator iter2 (messages.begin()); + iter2!=messages.end(); ++iter2) { - document->getReport (log)->add (*iter); - emit loadMessage (document, iter->mMessage); + document->getReport (log)->add (*iter2); + emit loadMessage (document, iter2->mMessage); } } diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 3fba2cd85..10faf3032 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -119,11 +119,11 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - CSMWorld::RecordBase::State state = iter->mState; + CSMWorld::RecordBase::State recState = iter->mState; - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly || - state==CSMWorld::RecordBase::State_Deleted) + if (recState==CSMWorld::RecordBase::State_Modified || + recState==CSMWorld::RecordBase::State_ModifiedOnly || + recState==CSMWorld::RecordBase::State_Deleted) { infoModified = true; break; diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 7936a1ae2..52de60280 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -313,15 +313,15 @@ boost::shared_ptr CSMFilter::Parser::parseNAry (const Token& ke nodes.push_back (node); - Token token = getNextToken(); + Token token2 = getNextToken(); - if (!token || (token.mType!=Token::Type_Close && token.mType!=Token::Type_Comma)) + if (!token2 || (token2.mType!=Token::Type_Close && token2.mType!=Token::Type_Comma)) { error(); return boost::shared_ptr(); } - if (token.mType==Token::Type_Close) + if (token2.mType==Token::Type_Close) break; } diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 52e1e6964..d936be593 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -183,11 +183,11 @@ void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& mes CSMWorld::LandTexture texture = mState.mSource.getData().getLandTextures().getRecord (index).get(); - std::ostringstream stream; - stream << mNext->second-1 << "_0"; + std::ostringstream stream2; + stream2 << mNext->second-1 << "_0"; texture.mIndex = mNext->second-1; - texture.mId = stream.str(); + texture.mId = stream2.str(); CSMWorld::Record newRecord ( CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture); diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 7f80eaa8e..e6acf77de 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -172,9 +172,9 @@ void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& reco if (index==-1) { - int index = mIdCollection->getAppendIndex (id, type); + int index2 = mIdCollection->getAppendIndex (id, type); - beginInsertRows (QModelIndex(), index, index); + beginInsertRows (QModelIndex(), index2, index2); mIdCollection->appendRecord (record, type); diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 60c613041..9eaa744d2 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -19,31 +19,31 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record2.mBase : record2.mModified) = record; - int index = -1; + int index2 = -1; std::string topic = Misc::StringUtils::lowerCase (record2.get().mTopicId); if (!record2.get().mPrev.empty()) { - index = getInfoIndex (record2.get().mPrev, topic); + index2 = getInfoIndex (record2.get().mPrev, topic); - if (index!=-1) - ++index; + if (index2!=-1) + ++index2; } - if (index==-1 && !record2.get().mNext.empty()) + if (index2==-1 && !record2.get().mNext.empty()) { - index = getInfoIndex (record2.get().mNext, topic); + index2 = getInfoIndex (record2.get().mNext, topic); } - if (index==-1) + if (index2==-1) { Range range = getTopicRange (topic); - index = std::distance (getRecords().begin(), range.second); + index2 = std::distance (getRecords().begin(), range.second); } - insertRecord (record2, index); + insertRecord (record2, index2); } else { diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index f8818807b..a60ee3d70 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -74,9 +74,9 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool std::cerr << "Position: #" << index.first << " " << index.second <<", Target #"<< mref.mTarget[0] << " " << mref.mTarget[1] << std::endl; - std::ostringstream stream; - stream << "#" << mref.mTarget[0] << " " << mref.mTarget[1]; - ref.mCell = stream.str(); // overwrite + std::ostringstream stream2; + stream2 << "#" << mref.mTarget[0] << " " << mref.mTarget[1]; + ref.mCell = stream2.str(); // overwrite } } } diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index a5e813338..9045be584 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -878,10 +878,10 @@ void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, Univers if (index==-1) { // new record - int index = mData.getAppendIndex (type); + int newIndex = mData.getAppendIndex (type); mData.appendRecord (type, id, base); - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); + RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (newIndex); mData.load (localIndex, reader, base); diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 8bfd83248..fc5b127dc 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -26,22 +26,22 @@ CSMWorld::Resources::Resources (const std::string& baseDirectory, UniversalId::T Ogre::StringVectorPtr resources = Ogre::ResourceGroupManager::getSingleton().listResourceNames (*iter); - for (Ogre::StringVector::const_iterator iter (resources->begin()); - iter!=resources->end(); ++iter) + for (Ogre::StringVector::const_iterator iter2 (resources->begin()); + iter2!=resources->end(); ++iter2) { - if (static_cast (iter->size())substr (0, baseSize)!=mBaseDirectory || - ((*iter)[baseSize]!='/' && (*iter)[baseSize]!='\\')) + if (static_cast (iter2->size())substr (0, baseSize)!=mBaseDirectory || + ((*iter2)[baseSize]!='/' && (*iter2)[baseSize]!='\\')) continue; if (extensions) { - std::string::size_type index = iter->find_last_of ('.'); + std::string::size_type index = iter2->find_last_of ('.'); if (index==std::string::npos) continue; - std::string extension = iter->substr (index+1); + std::string extension = iter2->substr (index+1); int i = 0; @@ -53,7 +53,7 @@ CSMWorld::Resources::Resources (const std::string& baseDirectory, UniversalId::T continue; } - std::string file = iter->substr (baseSize+1); + std::string file = iter2->substr (baseSize+1); mFiles.push_back (file); std::replace (file.begin(), file.end(), '\\', '/'); mIndex.insert (std::make_pair ( diff --git a/apps/opencs/view/render/textoverlay.cpp b/apps/opencs/view/render/textoverlay.cpp index 14b60be93..21e898738 100644 --- a/apps/opencs/view/render/textoverlay.cpp +++ b/apps/opencs/view/render/textoverlay.cpp @@ -105,7 +105,7 @@ TextOverlay::TextOverlay(const Ogre::MovableObject* obj, const Ogre::Camera* cam "TransOverlayMaterial"); if(mQuadMaterial.isNull()) { - Ogre::MaterialPtr mQuadMaterial = Ogre::MaterialManager::getSingleton().create( + mQuadMaterial = Ogre::MaterialManager::getSingleton().create( "TransOverlayMaterial", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true ); Ogre::Pass *pass = mQuadMaterial->getTechnique( 0 )->getPass( 0 ); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 14b7fcac3..54e58a7ca 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -667,14 +667,14 @@ void CSVWorld::EditWidget::remake(int row) int displayRole = tree->nestedHeaderData (i, col, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt(); - CSMWorld::ColumnBase::Display display = + CSMWorld::ColumnBase::Display display2 = static_cast (displayRole); - mNestedTableDispatcher->makeDelegate (display); + mNestedTableDispatcher->makeDelegate (display2); // FIXME: assumed all columns are editable QWidget* editor = - mNestedTableDispatcher->makeEditor (display, tree->index (0, col, tree->index(row, i))); + mNestedTableDispatcher->makeEditor (display2, tree->index (0, col, tree->index(row, i))); if (editor) { mNestedTableMapper->addMapping (editor, col); @@ -699,7 +699,7 @@ void CSVWorld::EditWidget::remake(int row) label->setEnabled(false); } - createEditorContextMenu(editor, display, row); + createEditorContextMenu(editor, display2, row); } } mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i))); From 429075101038564e8d96f9ca251540cfb21b59d2 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 22 Nov 2015 19:17:10 +1100 Subject: [PATCH 257/441] Fix warning C4457 (declaration hides function parameter) --- apps/opencs/view/world/referenceablecreator.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index 1357ca46f..836e8ac7d 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -26,10 +26,10 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd for (std::vector::const_iterator iter (types.begin()); iter!=types.end(); ++iter) { - CSMWorld::UniversalId id (*iter, ""); + CSMWorld::UniversalId id2 (*iter, ""); - mType->addItem (QIcon (id.getIcon().c_str()), id.getTypeName().c_str(), - static_cast (id.getType())); + mType->addItem (QIcon (id2.getIcon().c_str()), id2.getTypeName().c_str(), + static_cast (id2.getType())); } insertBeforeButtons (mType, false); From 19eed6f4c4ff8219e504ae7df99fec6fc5557290 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 22 Nov 2015 19:23:11 +1100 Subject: [PATCH 258/441] Fix warning C4458 (declaration of 'data' hides class member) --- apps/opencs/view/filter/filterbox.cpp | 4 ++-- apps/opencs/view/render/pagedworldspacewidget.cpp | 8 ++++---- apps/opencs/view/render/unpagedworldspacewidget.cpp | 8 ++++---- apps/opencs/view/render/worldspacewidget.cpp | 6 +++--- apps/opencs/view/world/dragrecordtable.cpp | 4 ++-- apps/opencs/view/world/scenesubview.cpp | 12 ++++++------ apps/opencs/view/world/tablesubview.cpp | 8 ++++---- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index c6c6cc6cc..e50191679 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -38,9 +38,9 @@ void CSVFilter::FilterBox::dropEvent (QDropEvent* event) if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; - std::vector data = mime->getData(); + std::vector mimeData = mime->getData(); - emit recordDropped(data, event->proposedAction()); + emit recordDropped(mimeData, event->proposedAction()); } void CSVFilter::FilterBox::dragEnterEvent (QDragEnterEvent* event) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index cd247afe5..01d5d0d76 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -536,18 +536,18 @@ std::pair< int, int > CSVRender::PagedWorldspaceWidget::getCoordinatesFromId (co } bool CSVRender::PagedWorldspaceWidget::handleDrop ( - const std::vector< CSMWorld::UniversalId >& data, DropType type) + const std::vector< CSMWorld::UniversalId >& dropData, DropType type) { - if (WorldspaceWidget::handleDrop (data, type)) + if (WorldspaceWidget::handleDrop (dropData, type)) return true; if (type!=Type_CellsExterior) return false; bool selectionChanged = false; - for (unsigned i = 0; i < data.size(); ++i) + for (unsigned i = 0; i < dropData.size(); ++i) { - std::pair coordinates(getCoordinatesFromId(data[i].getId())); + std::pair coordinates(getCoordinatesFromId(dropData[i].getId())); if (mSelection.add(CSMWorld::CellCoordinates(coordinates.first, coordinates.second))) { selectionChanged = true; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 321c79460..7455f8736 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -84,21 +84,21 @@ void CSVRender::UnpagedWorldspaceWidget::cellRowsAboutToBeRemoved (const QModelI emit closeRequest(); } -bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector& data, DropType type) +bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector& dropData, DropType type) { - if (WorldspaceWidget::handleDrop (data, type)) + if (WorldspaceWidget::handleDrop (dropData, type)) return true; if (type!=Type_CellsInterior) return false; - mCellId = data.begin()->getId(); + mCellId = dropData.begin()->getId(); Cell *cell = new Cell (getDocument(), getSceneManager(), mCellId, getDocument().getPhysics()); connect (cell->getSignalHandler(), SIGNAL(flagAsModified()), this, SLOT(flagAsModSlot())); mCell.reset (cell); update(); - emit cellChanged(*data.begin()); + emit cellChanged(*dropData.begin()); return true; } diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 3c1ef5c64..53791e05e 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -233,15 +233,15 @@ CSVRender::WorldspaceWidget::dropRequirments return ignored; } -bool CSVRender::WorldspaceWidget::handleDrop (const std::vector& data, +bool CSVRender::WorldspaceWidget::handleDrop (const std::vector& dropData, DropType type) { if (type==Type_DebugProfile) { if (mRun) { - for (std::vector::const_iterator iter (data.begin()); - iter!=data.end(); ++iter) + for (std::vector::const_iterator iter (dropData.begin()); + iter!=dropData.end(); ++iter) mRun->addProfile (iter->getId()); } diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index a5f933283..03f5a8739 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -67,8 +67,8 @@ void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) CSMWorld::ColumnBase::Display display = getIndexDisplayType(index); if (CSVWorld::DragDropUtils::canAcceptData(*event, display)) { - const CSMWorld::TableMimeData *data = CSVWorld::DragDropUtils::getTableMimeData(*event); - if (data->fromDocument(mDocument)) + const CSMWorld::TableMimeData *mimeData = CSVWorld::DragDropUtils::getTableMimeData(*event); + if (mimeData->fromDocument(mDocument)) { CSMWorld::UniversalId id = CSVWorld::DragDropUtils::getAcceptedData(*event, display); QVariant newIndexData = QString::fromUtf8(id.getId().c_str()); diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index c2f3442f8..75ace32bb 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -187,18 +187,18 @@ void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection emit updateTitle(); } -void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalId >& data) +void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalId >& dropData) { CSVRender::PagedWorldspaceWidget* pagedNewWidget = NULL; CSVRender::UnpagedWorldspaceWidget* unPagedNewWidget = NULL; CSVWidget::SceneToolbar* toolbar = NULL; - CSVRender::WorldspaceWidget::DropType type = CSVRender::WorldspaceWidget::getDropType (data); + CSVRender::WorldspaceWidget::DropType type = CSVRender::WorldspaceWidget::getDropType (dropData); switch (mScene->getDropRequirements (type)) { case CSVRender::WorldspaceWidget::canHandle: - mScene->handleDrop (data, type); + mScene->handleDrop (dropData, type); break; case CSVRender::WorldspaceWidget::needPaged: @@ -206,15 +206,15 @@ void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalI toolbar = makeToolbar(pagedNewWidget, widget_Paged); makeConnections(pagedNewWidget); replaceToolbarAndWorldspace(pagedNewWidget, toolbar); - mScene->handleDrop (data, type); + mScene->handleDrop (dropData, type); break; case CSVRender::WorldspaceWidget::needUnpaged: - unPagedNewWidget = new CSVRender::UnpagedWorldspaceWidget(data.begin()->getId(), mDocument, this); + unPagedNewWidget = new CSVRender::UnpagedWorldspaceWidget(dropData.begin()->getId(), mDocument, this); toolbar = makeToolbar(unPagedNewWidget, widget_Unpaged); makeConnections(unPagedNewWidget); replaceToolbarAndWorldspace(unPagedNewWidget, toolbar); - cellSelectionChanged(*(data.begin())); + cellSelectionChanged(*(dropData.begin())); break; case CSVRender::WorldspaceWidget::ignored: diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 81f78cdad..e424ab85f 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -203,14 +203,14 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) { if (QDropEvent* drop = dynamic_cast(event)) { - const CSMWorld::TableMimeData* data = dynamic_cast(drop->mimeData()); - if (!data) // May happen when non-records (e.g. plain text) are dragged and dropped + const CSMWorld::TableMimeData* mimeData = dynamic_cast(drop->mimeData()); + if (!mimeData) // May happen when non-records (e.g. plain text) are dragged and dropped return false; - bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); + bool handled = mimeData->holdsType(CSMWorld::UniversalId::Type_Filter); if (handled) { - mFilterBox->setRecordFilter(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); + mFilterBox->setRecordFilter(mimeData->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); } return handled; } From 368dd9bd8de36afe042e58abcadf36b57e744c90 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 4 Nov 2015 20:31:23 +0100 Subject: [PATCH 259/441] Compiler: remove unused mNameStartingWithDigit (cherry picked from commit f7d0d06134c8b82c5142451d9ff256c1effcce74) --- components/compiler/scanner.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 847895978..fe867feba 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -37,7 +37,6 @@ namespace Compiler float mPutbackFloat; std::string mPutbackName; TokenLoc mPutbackLoc; - bool mNameStartingWithDigit; public: From 70cb6f023877b5f27b580419fb040bb2be83580e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 9 Nov 2015 09:07:18 +0100 Subject: [PATCH 260/441] fixed an interference with script warning mode and error downgrading (Fixes #2990) (cherry picked from commit b61b732207e9ec5e48023987e368156375916b57) --- components/compiler/errorhandler.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/compiler/errorhandler.cpp b/components/compiler/errorhandler.cpp index a987a86da..7f02255db 100644 --- a/components/compiler/errorhandler.cpp +++ b/components/compiler/errorhandler.cpp @@ -32,7 +32,10 @@ namespace Compiler void ErrorHandler::warning (const std::string& message, const TokenLoc& loc) { - if (mWarningsMode==1) + if (mWarningsMode==1 || + // temporarily change from mode 2 to mode 1 if error downgrading is enabled to + // avoid infinite recursion + (mWarningsMode==2 && mDowngradeErrors)) { ++mWarnings; report (message, loc, WarningMessage); From e9a8eac6af7b68e9c1859c472f56d04e304e6459 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 7 Jul 2015 15:37:42 +0300 Subject: [PATCH 261/441] Add NAME and DELE handling to ESM records. Changed records are those where DELE is located after NAME sub-record. And DELE is the last sub-record. (cherry picked from commit 926c825d0c7f0a373fb6bd22d88d0df850407f3c) Conflicts: components/esm/loadstat.cpp components/esm/loadstat.hpp components/esm/util.hpp --- components/esm/loadacti.cpp | 15 +++++++++++++++ components/esm/loadacti.hpp | 2 ++ components/esm/loadalch.cpp | 16 ++++++++++++++++ components/esm/loadalch.hpp | 2 ++ components/esm/loadappa.cpp | 15 +++++++++++++++ components/esm/loadappa.hpp | 2 ++ components/esm/loadarmo.cpp | 16 ++++++++++++++++ components/esm/loadarmo.hpp | 2 ++ components/esm/loadbody.cpp | 16 ++++++++++++++++ components/esm/loadbody.hpp | 2 ++ components/esm/loadbook.cpp | 15 +++++++++++++++ components/esm/loadbook.hpp | 2 ++ components/esm/loadclas.cpp | 16 ++++++++++++++++ components/esm/loadclas.hpp | 2 ++ components/esm/loadclot.cpp | 16 ++++++++++++++++ components/esm/loadclot.hpp | 2 ++ components/esm/loadcont.cpp | 16 ++++++++++++++++ components/esm/loadcont.hpp | 2 ++ components/esm/loadcrea.cpp | 15 +++++++++++++++ components/esm/loadcrea.hpp | 3 ++- components/esm/loaddoor.cpp | 15 +++++++++++++++ components/esm/loaddoor.hpp | 2 ++ components/esm/loadench.cpp | 17 +++++++++++++++++ components/esm/loadench.hpp | 2 ++ components/esm/loadfact.cpp | 16 ++++++++++++++++ components/esm/loadfact.hpp | 2 ++ components/esm/loadingr.cpp | 16 ++++++++++++++++ components/esm/loadingr.hpp | 2 ++ components/esm/loadlevlist.cpp | 15 +++++++++++++++ components/esm/loadlevlist.hpp | 2 ++ components/esm/loadligh.cpp | 15 +++++++++++++++ components/esm/loadligh.hpp | 2 ++ components/esm/loadlock.cpp | 15 +++++++++++++++ components/esm/loadlock.hpp | 2 ++ components/esm/loadmisc.cpp | 15 +++++++++++++++ components/esm/loadmisc.hpp | 2 ++ components/esm/loadnpc.cpp | 15 +++++++++++++++ components/esm/loadnpc.hpp | 2 ++ components/esm/loadprob.cpp | 15 +++++++++++++++ components/esm/loadprob.hpp | 2 ++ components/esm/loadrepa.cpp | 15 +++++++++++++++ components/esm/loadrepa.hpp | 2 ++ components/esm/loadsndg.cpp | 15 +++++++++++++++ components/esm/loadsndg.hpp | 2 ++ components/esm/loadsoun.cpp | 16 ++++++++++++++++ components/esm/loadsoun.hpp | 2 ++ components/esm/loadspel.cpp | 17 +++++++++++++++++ components/esm/loadspel.hpp | 2 ++ components/esm/loadstat.cpp | 15 ++++++++++++++- components/esm/loadstat.hpp | 2 +- components/esm/loadweap.cpp | 16 ++++++++++++++++ components/esm/loadweap.hpp | 2 ++ components/esm/util.cpp | 20 ++++++++++++++++++++ components/esm/util.hpp | 9 +++++++++ 54 files changed, 483 insertions(+), 3 deletions(-) create mode 100644 components/esm/util.cpp diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index b5adce550..295d35f7e 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Activator::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + while (esm.hasMoreSubs()) { esm.getSubName(); @@ -32,6 +39,13 @@ namespace ESM } void Activator::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); @@ -42,5 +56,6 @@ namespace ESM mName.clear(); mScript.clear(); mModel.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp index d9a55023b..406512a22 100644 --- a/components/esm/loadacti.hpp +++ b/components/esm/loadacti.hpp @@ -17,6 +17,8 @@ struct Activator std::string mId, mName, mScript, mModel; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index 18db512c0..12078a5f7 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -11,6 +12,13 @@ namespace ESM void Potion::load(ESMReader &esm) { mEffects.mList.clear(); + + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -46,6 +54,13 @@ namespace ESM } void Potion::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("TEXT", mIcon); esm.writeHNOCString("SCRI", mScript); @@ -64,5 +79,6 @@ namespace ESM mIcon.clear(); mScript.clear(); mEffects.mList.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index b90a7c448..3d1e6dee7 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -33,6 +33,8 @@ struct Potion std::string mId, mName, mModel, mIcon, mScript; EffectList mEffects; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index f2c82aacf..9107e5a8e 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Apparatus::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -43,6 +50,13 @@ void Apparatus::load(ESMReader &esm) void Apparatus::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); esm.writeHNT("AADT", mData, 16); @@ -60,5 +74,6 @@ void Apparatus::save(ESMWriter &esm) const mIcon.clear(); mScript.clear(); mName.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp index f18b04648..62d8561a1 100644 --- a/components/esm/loadappa.hpp +++ b/components/esm/loadappa.hpp @@ -38,6 +38,8 @@ struct Apparatus AADTstruct mData; std::string mId, mModel, mIcon, mScript, mName; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index 066551d6f..626893d00 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -41,6 +42,13 @@ namespace ESM void Armor::load(ESMReader &esm) { mParts.mParts.clear(); + + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -80,6 +88,13 @@ namespace ESM void Armor::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); @@ -103,5 +118,6 @@ namespace ESM mIcon.clear(); mScript.clear(); mEnchant.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index 54416fd31..eb911254f 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -96,6 +96,8 @@ struct Armor std::string mId, mName, mModel, mIcon, mScript, mEnchant; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index ed24ded57..644eb3b50 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -11,6 +12,12 @@ namespace ESM void BodyPart::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -38,6 +45,13 @@ void BodyPart::load(ESMReader &esm) } void BodyPart::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mRace); esm.writeHNT("BYDT", mData, 4); @@ -52,5 +66,7 @@ void BodyPart::save(ESMWriter &esm) const mModel.clear(); mRace.clear(); + + mIsDeleted = false; } } diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index c48c31305..8e1e81f82 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -60,6 +60,8 @@ struct BodyPart BYDTstruct mData; std::string mId, mModel, mRace; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index 47f52fc31..aec361873 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Book::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -48,6 +55,13 @@ namespace ESM } void Book::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("BKDT", mData, 20); @@ -70,5 +84,6 @@ namespace ESM mScript.clear(); mEnchant.clear(); mText.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp index 6211b3e45..2f374c4b2 100644 --- a/components/esm/loadbook.hpp +++ b/components/esm/loadbook.hpp @@ -28,6 +28,8 @@ struct Book std::string mName, mModel, mIcon, mScript, mEnchant, mText; std::string mId; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index 66acaea72..df47a1a33 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -5,6 +5,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -41,6 +42,12 @@ namespace ESM void Class::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -69,6 +76,13 @@ namespace ESM } void Class::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNOCString("FNAM", mName); esm.writeHNT("CLDT", mData, 60); esm.writeHNOString("DESC", mDescription); @@ -87,5 +101,7 @@ namespace ESM for (int i=0; i<5; ++i) for (int i2=0; i2<2; ++i2) mData.mSkills[i][i2] = 0; + + mIsDeleted = false; } } diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index 972b48e88..b61913734 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -73,6 +73,8 @@ struct Class std::string mId, mName, mDescription; CLDTstruct mData; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 5f49b5e70..1c73c4f21 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -11,6 +12,13 @@ namespace ESM void Clothing::load(ESMReader &esm) { mParts.mParts.clear(); + + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -50,6 +58,13 @@ namespace ESM void Clothing::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("CTDT", mData, 12); @@ -74,5 +89,6 @@ namespace ESM mIcon.clear(); mEnchant.clear(); mScript.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp index 6945f224a..8b4a8b3bb 100644 --- a/components/esm/loadclot.hpp +++ b/components/esm/loadclot.hpp @@ -48,6 +48,8 @@ struct Clothing std::string mId, mName, mModel, mIcon, mEnchant, mScript; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 3481189c3..f54fe66e2 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -27,6 +28,13 @@ namespace ESM void Container::load(ESMReader &esm) { mInventory.mList.clear(); + + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasWeight = false; bool hasFlags = false; while (esm.hasMoreSubs()) @@ -71,6 +79,13 @@ namespace ESM void Container::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("CNDT", mWeight, 4); @@ -89,5 +104,6 @@ namespace ESM mWeight = 0; mFlags = 0x8; // set default flag value mInventory.mList.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index ab587f935..51166b8d4 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -52,6 +52,8 @@ struct Container int mFlags; InventoryList mInventory; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index fb235e6b3..f1590f19b 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -17,6 +18,12 @@ namespace ESM { mSpells.mList.clear(); mTransport.mList.clear(); + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + mScale = 1.f; mHasAI = false; bool hasNpdt = false; @@ -84,6 +91,13 @@ namespace ESM { void Creature::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("CNAM", mOriginal); esm.writeHNOCString("FNAM", mName); @@ -132,6 +146,7 @@ namespace ESM { mAiData.mServices = 0; mAiPackage.mList.clear(); mTransport.mList.clear(); + mIsDeleted = false; } const std::vector& Creature::getTransport() const diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 47e5954a5..fb43f6272 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -91,12 +91,13 @@ struct Creature InventoryList mInventory; SpellList mSpells; - bool mHasAI; AIData mAiData; AIPackageList mAiPackage; Transport mTransport; + bool mIsDeleted; + const std::vector& getTransport() const; void load(ESMReader &esm); diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index f446eed61..6ee43fd1a 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Door::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + while (esm.hasMoreSubs()) { esm.getSubName(); @@ -39,6 +46,13 @@ namespace ESM void Door::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); @@ -53,5 +67,6 @@ namespace ESM mScript.clear(); mOpenSound.clear(); mCloseSound.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index 3073f4e9d..b0326a744 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -17,6 +17,8 @@ struct Door std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 54690d9a0..88d9e0f2e 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -11,6 +12,13 @@ namespace ESM void Enchantment::load(ESMReader &esm) { mEffects.mList.clear(); + + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -36,6 +44,13 @@ void Enchantment::load(ESMReader &esm) void Enchantment::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNT("ENDT", mData, 16); mEffects.save(esm); } @@ -48,5 +63,7 @@ void Enchantment::save(ESMWriter &esm) const mData.mAutocalc = 0; mEffects.mList.clear(); + + mIsDeleted = false; } } diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index cfcdd4edc..c9b19e31b 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -42,6 +42,8 @@ struct Enchantment ENDTstruct mData; EffectList mEffects; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 006ca0ce0..9149c2a30 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -5,6 +5,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -32,6 +33,12 @@ void Faction::load(ESMReader &esm) for (int i=0;i<10;++i) mRanks[i].clear(); + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + int rankCounter=0; bool hasData = false; while (esm.hasMoreSubs()) @@ -71,6 +78,13 @@ void Faction::load(ESMReader &esm) } void Faction::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNOCString("FNAM", mName); for (int i = 0; i < 10; i++) @@ -109,5 +123,7 @@ void Faction::save(ESMWriter &esm) const mData.mSkills[i] = 0; mReactions.clear(); + + mIsDeleted = false; } } diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 8645e23fd..fec13b1ca 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -62,6 +62,8 @@ struct Faction // Name of faction ranks (may be empty for NPC factions) std::string mRanks[10]; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 7e0cc3168..8bf0278f7 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Ingredient::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -67,6 +74,13 @@ namespace ESM void Ingredient::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("IRDT", mData, 56); @@ -89,5 +103,7 @@ namespace ESM mModel.clear(); mIcon.clear(); mScript.clear(); + + mIsDeleted = false; } } diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index 5846a9780..69a650180 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -31,6 +31,8 @@ struct Ingredient IRDTstruct mData; std::string mId, mName, mModel, mIcon, mScript; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index ca5c5d74d..c8cb110a6 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -3,12 +3,19 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { void LevelledListBase::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + esm.getHNT(mFlags, "DATA"); esm.getHNT(mChanceNone, "NNAM"); @@ -42,6 +49,13 @@ namespace ESM } void LevelledListBase::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); esm.writeHNT("INDX", mList.size()); @@ -58,6 +72,7 @@ namespace ESM mFlags = 0; mChanceNone = 0; mList.clear(); + mIsDeleted = false; } unsigned int CreatureLevList::sRecordId = REC_LEVC; diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index dc6fcda5e..4165275b2 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -36,6 +36,8 @@ struct LevelledListBase std::vector mList; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index 26d70d964..bd91e096c 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Light::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -45,6 +52,13 @@ namespace ESM } void Light::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("ITEX", mIcon); @@ -66,5 +80,6 @@ namespace ESM mModel.clear(); mIcon.clear(); mName.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index ed8c36665..76274e6f4 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -47,6 +47,8 @@ struct Light std::string mSound, mScript, mModel, mIcon, mName, mId; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 2747a6f78..c1e866b58 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Lockpick::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = true; while (esm.hasMoreSubs()) { @@ -43,6 +50,13 @@ namespace ESM void Lockpick::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); @@ -61,5 +75,6 @@ namespace ESM mModel.clear(); mIcon.clear(); mScript.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp index 0d678cd64..af18773ed 100644 --- a/components/esm/loadlock.hpp +++ b/components/esm/loadlock.hpp @@ -27,6 +27,8 @@ struct Lockpick Data mData; std::string mId, mName, mModel, mIcon, mScript; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index 81c094f2b..11d2f2a05 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Miscellaneous::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -41,6 +48,13 @@ namespace ESM void Miscellaneous::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("MCDT", mData, 12); @@ -57,5 +71,6 @@ namespace ESM mModel.clear(); mIcon.clear(); mScript.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadmisc.hpp b/components/esm/loadmisc.hpp index 6e0b4e01b..5d5e9f66f 100644 --- a/components/esm/loadmisc.hpp +++ b/components/esm/loadmisc.hpp @@ -32,6 +32,8 @@ struct Miscellaneous std::string mId, mName, mModel, mIcon, mScript; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 67a437176..96957f1e2 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -17,6 +18,12 @@ namespace ESM mTransport.mList.clear(); mAiPackage.mList.clear(); + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasNpdt = false; bool hasFlags = false; mHasAI = false; @@ -103,6 +110,13 @@ namespace ESM } void NPC::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNOCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNCString("RNAM", mRace); @@ -183,6 +197,7 @@ namespace ESM mScript.clear(); mHair.clear(); mHead.clear(); + mIsDeleted = false; } int NPC::getFactionRank() const diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 9bda9560e..7b75cb178 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -130,6 +130,8 @@ struct NPC // body parts std::string mHair, mHead; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index c5f80c584..12f7ad00a 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Probe::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = true; while (esm.hasMoreSubs()) { @@ -43,6 +50,13 @@ namespace ESM void Probe::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); @@ -61,5 +75,6 @@ namespace ESM mModel.clear(); mIcon.clear(); mScript.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp index c737757aa..9daab6b1b 100644 --- a/components/esm/loadprob.hpp +++ b/components/esm/loadprob.hpp @@ -27,6 +27,8 @@ struct Probe Data mData; std::string mId, mName, mModel, mIcon, mScript; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index f90f9e39d..608536f02 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Repair::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = true; while (esm.hasMoreSubs()) { @@ -43,6 +50,13 @@ void Repair::load(ESMReader &esm) void Repair::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); @@ -61,5 +75,6 @@ void Repair::save(ESMWriter &esm) const mModel.clear(); mIcon.clear(); mScript.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp index e765bc93a..a660574be 100644 --- a/components/esm/loadrepa.hpp +++ b/components/esm/loadrepa.hpp @@ -27,6 +27,8 @@ struct Repair Data mData; std::string mId, mName, mModel, mIcon, mScript; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index 5ee6f5245..f92c4eee8 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void SoundGenerator::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -36,6 +43,13 @@ namespace ESM } void SoundGenerator::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNT("DATA", mType, 4); esm.writeHNOCString("CNAM", mCreature); esm.writeHNOCString("SNAM", mSound); @@ -46,5 +60,6 @@ namespace ESM mType = LeftFoot; mCreature.clear(); mSound.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index 056958f0a..e486976f5 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -36,6 +36,8 @@ struct SoundGenerator std::string mId, mCreature, mSound; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 690c1b448..19e8a5d80 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Sound::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -34,6 +41,13 @@ namespace ESM void Sound::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNOCString("FNAM", mSound); esm.writeHNT("DATA", mData, 3); } @@ -45,5 +59,7 @@ namespace ESM mData.mVolume = 128; mData.mMinRange = 0; mData.mMaxRange = 255; + + mIsDeleted = false; } } diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index ff2202ca7..95c89b29d 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -23,6 +23,8 @@ struct Sound SOUNstruct mData; std::string mId, mSound; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 96c048e0a..cac06b200 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -11,6 +12,13 @@ namespace ESM void Spell::load(ESMReader &esm) { mEffects.mList.clear(); + + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -39,6 +47,13 @@ namespace ESM void Spell::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNOCString("FNAM", mName); esm.writeHNT("SPDT", mData, 12); mEffects.save(esm); @@ -53,5 +68,7 @@ namespace ESM mName.clear(); mEffects.mList.clear(); + + mIsDeleted = false; } } diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index 491da1d17..2aba131ad 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -45,6 +45,8 @@ struct Spell std::string mId, mName; EffectList mEffects; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index ed90b0475..a49caba89 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,17 +11,29 @@ namespace ESM void Static::load(ESMReader &esm) { - mPersistent = (esm.getRecordFlags() & 0x0400) != 0; + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } mModel = esm.getHNString("MODL"); } void Static::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); } void Static::blank() { mModel.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index 21a9e66e8..c4306ad8f 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -28,7 +28,7 @@ struct Static std::string mId, mModel; - bool mPersistent; + bool mIsDeleted; void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 981a5815a..66d65d0f5 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,12 @@ namespace ESM void Weapon::load(ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + bool hasData = false; while (esm.hasMoreSubs()) { @@ -45,6 +52,13 @@ namespace ESM } void Weapon::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("WPDT", mData, 32); @@ -72,5 +86,7 @@ namespace ESM mIcon.clear(); mEnchant.clear(); mScript.clear(); + + mIsDeleted = false; } } diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index f66e9f3a6..30dfbc92a 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -69,6 +69,8 @@ struct Weapon std::string mId, mName, mModel, mIcon, mEnchant, mScript; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/util.cpp b/components/esm/util.cpp new file mode 100644 index 000000000..c20bd4d51 --- /dev/null +++ b/components/esm/util.cpp @@ -0,0 +1,20 @@ +#include "util.hpp" + +namespace ESM +{ + bool readDeleSubRecord(ESMReader &esm) + { + if (esm.isNextSub("DELE")) + { + esm.getSubName(); + esm.skipHSub(); + return true; + } + return false; + } + + void writeDeleSubRecord(ESMWriter &esm) + { + esm.writeHNString("DELE", ""); + } +} diff --git a/components/esm/util.hpp b/components/esm/util.hpp index bb7f3cf7c..61cb13892 100644 --- a/components/esm/util.hpp +++ b/components/esm/util.hpp @@ -1,9 +1,15 @@ #ifndef OPENMW_ESM_UTIL_H #define OPENMW_ESM_UTIL_H +#include + #include #include +#include "esmreader.hpp" +#include "esmwriter.hpp" +#include "loadbsgn.hpp" + namespace ESM { @@ -46,6 +52,9 @@ struct Vector3 } }; +bool readDeleSubRecord(ESMReader &esm); +void writeDeleSubRecord(ESMWriter &esm); + } #endif From 44c36a00f8dedc766555f3d76013a53add1e4de0 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 7 Jul 2015 15:49:55 +0300 Subject: [PATCH 262/441] Add NAME and DELE handling to ESM records. Changed records are those where DELE is inserted at the beginning of a record (before NAME). The record has all required sub-records in this case. (cherry picked from commit 9ac20a33552e61182dbb90c5e2f0b34683720583) --- components/esm/loadbsgn.cpp | 11 +++++++++++ components/esm/loadbsgn.hpp | 2 ++ components/esm/loadltex.cpp | 9 +++++++++ components/esm/loadltex.hpp | 2 ++ components/esm/loadregn.cpp | 10 ++++++++++ components/esm/loadregn.hpp | 2 ++ 6 files changed, 36 insertions(+) diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index e0cd83ea0..7892bfc68 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -11,6 +12,10 @@ namespace ESM void BirthSign::load(ESMReader &esm) { mPowers.mList.clear(); + + mIsDeleted = readDeleSubRecord(esm); + mId = esm.getHNString("NAME"); + while (esm.hasMoreSubs()) { esm.getSubName(); @@ -37,6 +42,11 @@ void BirthSign::load(ESMReader &esm) void BirthSign::save(ESMWriter &esm) const { + if (mIsDeleted) + { + writeDeleSubRecord(esm); + } + esm.writeHNCString("NAME", mId); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("TNAM", mTexture); esm.writeHNOCString("DESC", mDescription); @@ -50,6 +60,7 @@ void BirthSign::save(ESMWriter &esm) const mDescription.clear(); mTexture.clear(); mPowers.mList.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index f91f91c97..41c70fb24 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -22,6 +22,8 @@ struct BirthSign // List of powers and abilities that come with this birth sign. SpellList mPowers; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index c3e2d50ff..0ee30b11c 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,11 +11,18 @@ namespace ESM void LandTexture::load(ESMReader &esm) { + mIsDeleted = readDeleSubRecord(esm); + mId = esm.getHNString("NAME"); esm.getHNT(mIndex, "INTV"); mTexture = esm.getHNString("DATA"); } void LandTexture::save(ESMWriter &esm) const { + if (mIsDeleted) + { + writeDeleSubRecord(esm); + } + esm.writeHNCString("NAME", mId); esm.writeHNT("INTV", mIndex); esm.writeHNCString("DATA", mTexture); } @@ -23,6 +31,7 @@ void LandTexture::blank() { mTexture.clear(); mIndex = -1; + mIsDeleted = false; } } diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index 50a788105..e019e269e 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -34,6 +34,8 @@ struct LandTexture std::string mId, mTexture; int mIndex; + bool mIsDeleted; + void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 1b08b7217..b0adc6f58 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -10,6 +11,8 @@ namespace ESM void Region::load(ESMReader &esm) { + mIsDeleted = readDeleSubRecord(esm); + mId = esm.getHNString("NAME"); mName = esm.getHNOString("FNAM"); esm.getSubNameIs("WEAT"); @@ -49,6 +52,11 @@ void Region::load(ESMReader &esm) } void Region::save(ESMWriter &esm) const { + if (mIsDeleted) + { + writeDeleSubRecord(esm); + } + esm.writeHNString("NAME", mId); esm.writeHNOCString("FNAM", mName); if (esm.getVersion() == VER_12) @@ -77,5 +85,7 @@ void Region::save(ESMWriter &esm) const mName.clear(); mSleepList.clear(); mSoundList.clear(); + + mIsDeleted = false; } } diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index 1e241fffb..921ab887b 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -51,6 +51,8 @@ struct Region std::vector mSoundList; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; From 1b21d1b5f7c1d4a3ed95c6ef8876d52fc037d9f1 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 7 Jul 2015 17:00:40 +0300 Subject: [PATCH 263/441] Add NAME and DELE handling to Script record (cherry picked from commit d2c15647a3f1a03ad3c34dd7eaf6fe6cfbcd35ab) --- components/esm/loadscpt.cpp | 12 ++++++++++++ components/esm/loadscpt.hpp | 2 ++ 2 files changed, 14 insertions(+) diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 60b4a3304..b2b7309db 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -63,6 +64,10 @@ namespace ESM mData = data.mData; mId = data.mName.toString(); + // In scripts DELE sub-record appears after a header. + // The script data is following after DELE in this case. + mIsDeleted = readDeleSubRecord(esm); + mVarNames.clear(); while (esm.hasMoreSubs()) @@ -109,6 +114,11 @@ namespace ESM esm.writeHNT("SCHD", data, 52); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + } + if (!mVarNames.empty()) { esm.startSubRecord("SCVR"); @@ -139,6 +149,8 @@ namespace ESM mScriptText = "Begin \"" + mId + "\"\n\nEnd " + mId + "\n"; else mScriptText = "Begin " + mId + "\n\nEnd " + mId + "\n"; + + mIsDeleted = false; } } diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index 56390f384..401dfe105 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -50,6 +50,8 @@ public: /// Script source code std::string mScriptText; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; From 93736e9a810a9549b32a963f22ff8d99ac1681cc Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 7 Jul 2015 19:18:42 +0300 Subject: [PATCH 264/441] Change DELE sub-record value to 0 (4 bytes) (cherry picked from commit 19ac4e942a9efb329143415a46aabaaf51cbe8ef) --- components/esm/util.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/esm/util.cpp b/components/esm/util.cpp index c20bd4d51..892113cda 100644 --- a/components/esm/util.cpp +++ b/components/esm/util.cpp @@ -1,5 +1,7 @@ #include "util.hpp" +#include + namespace ESM { bool readDeleSubRecord(ESMReader &esm) @@ -15,6 +17,6 @@ namespace ESM void writeDeleSubRecord(ESMWriter &esm) { - esm.writeHNString("DELE", ""); + esm.writeHNT("DELE", static_cast(0)); } } From d89de1ba30b14c8eb0a8547e9c89a35290e48b87 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 7 Jul 2015 19:42:24 +0300 Subject: [PATCH 265/441] Add NAME and DELE handling to Dialogue record (cherry picked from commit 0b537186e5b513cf7d6045a7c88e8c6b4d4afc7c) --- components/esm/loaddial.cpp | 27 +++++++++++++++++---------- components/esm/loaddial.hpp | 5 +++-- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index f2da8f377..77a04ca01 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -2,9 +2,12 @@ #include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -12,18 +15,19 @@ namespace ESM void Dialogue::load(ESMReader &esm) { + mIsDeleted = false; + + mId = esm.getHNString("NAME"); esm.getSubNameIs("DATA"); esm.getSubHeader(); int si = esm.getSubSize(); if (si == 1) esm.getT(mType); - else if (si == 4) + else if (si == 4) // The dialogue is deleted { - // These are just markers, their values are not used. - int i; - esm.getT(i); - esm.getHNT(i, "DELE"); - mType = Deleted; + int32_t empty; + esm.getT(empty); // Skip an empty DATA + mIsDeleted = readDeleSubRecord(esm); } else esm.fail("Unknown sub record size"); @@ -31,12 +35,15 @@ void Dialogue::load(ESMReader &esm) void Dialogue::save(ESMWriter &esm) const { - if (mType != Deleted) - esm.writeHNT("DATA", mType); + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + esm.writeHNT("DATA", static_cast(0)); + writeDeleSubRecord(esm); + } else { - esm.writeHNT("DATA", (int)1); - esm.writeHNT("DELE", (int)1); + esm.writeHNT("DATA", mType); } } diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 58598d353..ab8cf4bff 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -30,8 +30,7 @@ struct Dialogue Voice = 1, Greeting = 2, Persuasion = 3, - Journal = 4, - Deleted = -1 + Journal = 4 }; std::string mId; @@ -46,6 +45,8 @@ struct Dialogue // This is only used during the loading phase to speed up DialInfo merging. LookupMap mLookup; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; From 8c77cafc35940a700a3d5a4a52a8ee270b4f87f3 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 7 Jul 2015 19:57:08 +0300 Subject: [PATCH 266/441] Add DELE handling to Info record (cherry picked from commit 847614c26f372d5483bb96a3b30e90dd9358b28f) --- components/esm/loadinfo.cpp | 21 ++++++++++++++++++--- components/esm/loadinfo.hpp | 2 ++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index a2bade1c5..3394ddbad 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -19,11 +20,18 @@ void DialInfo::load(ESMReader &esm) // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings mSelects.clear(); - // Not present if deleted - if (esm.isNextSub("DATA")) { - esm.getHT(mData, 12); + // If the info is deleted, NAME and DELE sub-records are followed after NNAM + if (esm.isNextSub("NAME")) + { + esm.getSubName(); + mResponse = esm.getHString(); + mIsDeleted = readDeleSubRecord(esm); + return; } + esm.getSubNameIs("DATA"); + esm.getHT(mData, 12); + if (!esm.hasMoreSubs()) return; @@ -131,6 +139,13 @@ void DialInfo::save(ESMWriter &esm) const { esm.writeHNCString("PNAM", mPrev); esm.writeHNCString("NNAM", mNext); + if (mIsDeleted) + { + esm.writeHNCString("NAME", mResponse); + writeDeleSubRecord(esm); + return; + } + esm.writeHNT("DATA", mData, 12); esm.writeHNOCString("ONAM", mActor); esm.writeHNOCString("RNAM", mRace); diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 54003b0d9..8b4fae761 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -106,6 +106,8 @@ struct DialInfo REC_DELE = 0x454c4544 }; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; From 7dc0c9138f54c8612b8e8859bdb3d6dd4cfa1d0a Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 7 Jul 2015 20:41:39 +0300 Subject: [PATCH 267/441] Add NAME and DELE handling to Cell record (cherry picked from commit b667338a8fe5ffb78bc28151948f31e03dc99407) --- components/esm/loadcell.cpp | 23 +++++++++++++++-------- components/esm/loadcell.hpp | 3 +++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 94f4b0b6e..86b4e4edb 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -12,6 +12,7 @@ #include "esmwriter.hpp" #include "defs.hpp" #include "cellid.hpp" +#include "util.hpp" namespace { @@ -54,10 +55,17 @@ namespace ESM void Cell::load(ESMReader &esm, bool saveContext) { + loadName(esm); loadData(esm); loadCell(esm, saveContext); } +void Cell::loadName(ESMReader &esm) +{ + mName = esm.getHNString("NAME"); + mIsDeleted = readDeleSubRecord(esm); +} + void Cell::loadCell(ESMReader &esm, bool saveContext) { mRefNumCounter = 0; @@ -105,13 +113,6 @@ void Cell::loadCell(ESMReader &esm, bool saveContext) void Cell::loadData(ESMReader &esm) { - // Ignore this for now, it might mean we should delete the entire - // cell? - // TODO: treat the special case "another plugin moved this ref, but we want to delete it"! - if (esm.isNextSub("DELE")) { - esm.skipHSub(); - } - esm.getHNT(mData, "DATA", 12); } @@ -124,6 +125,12 @@ void Cell::postLoad(ESMReader &esm) void Cell::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mName); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + } + esm.writeHNT("DATA", mData, 12); if (mData.mFlags & Interior) { @@ -147,7 +154,7 @@ void Cell::save(ESMWriter &esm) const esm.writeHNT("NAM5", mMapColor); } - if (mRefNumCounter != 0) + if (mRefNumCounter != 0 && !mIsDeleted) esm.writeHNT("NAM0", mRefNumCounter); } diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 12fb8c068..b313435d6 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -112,11 +112,14 @@ struct Cell CellRefTracker mLeasedRefs; MovedCellRefTracker mMovedRefs; + bool mIsDeleted; + void postLoad(ESMReader &esm); // This method is left in for compatibility with esmtool. Parsing moved references currently requires // passing ESMStore, bit it does not know about this parameter, so we do it this way. void load(ESMReader &esm, bool saveContext = true); // Load everything (except references) + void loadName(ESMReader &esm); // Load NAME and checks for DELE void loadData(ESMReader &esm); // Load DATAstruct only void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except DATAstruct and references From 4f1601fe0d6f12672e849338b482905cbc2dc4be Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 7 Jul 2015 20:56:26 +0300 Subject: [PATCH 268/441] Add NAME handling to Race record (cherry picked from commit 8c3654af11e21ec8ca068c1581e69de707000f67) --- components/esm/loadrace.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index e88454d4c..3feb06c92 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -22,6 +22,8 @@ void Race::load(ESMReader &esm) { mPowers.mList.clear(); + mId = esm.getHNString("NAME"); + bool hasData = false; while (esm.hasMoreSubs()) { @@ -51,6 +53,7 @@ void Race::load(ESMReader &esm) } void Race::save(ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); esm.writeHNOCString("FNAM", mName); esm.writeHNT("RADT", mData, 140); mPowers.save(esm); From 1aa1336dcf0a2c2ef2cec3937a68fe51d5e30fed Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 7 Jul 2015 21:24:35 +0300 Subject: [PATCH 269/441] Remove redundant code (cherry picked from commit 30b42bf4c0a3c953703987ec333bd89ecd667f7e) --- components/esm/loadinfo.cpp | 1 - components/esm/util.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 3394ddbad..3b5e0ea3a 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -23,7 +23,6 @@ void DialInfo::load(ESMReader &esm) // If the info is deleted, NAME and DELE sub-records are followed after NNAM if (esm.isNextSub("NAME")) { - esm.getSubName(); mResponse = esm.getHString(); mIsDeleted = readDeleSubRecord(esm); return; diff --git a/components/esm/util.cpp b/components/esm/util.cpp index 892113cda..ff2436544 100644 --- a/components/esm/util.cpp +++ b/components/esm/util.cpp @@ -8,7 +8,6 @@ namespace ESM { if (esm.isNextSub("DELE")) { - esm.getSubName(); esm.skipHSub(); return true; } From 711d7879392d390282d87a06c91a7714feafa894 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Wed, 8 Jul 2015 00:04:14 +0300 Subject: [PATCH 270/441] Add NAME and DELE handling to Global record (cherry picked from commit 09a33580170076f14b2d46d72ad93d60fe52fc07) --- components/esm/loadglob.cpp | 16 ++++++++++++++++ components/esm/loadglob.hpp | 2 ++ 2 files changed, 18 insertions(+) diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index a78ed1a1b..6cc37d675 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -1,6 +1,9 @@ #include "loadglob.hpp" +#include "esmreader.hpp" +#include "esmwriter.hpp" #include "defs.hpp" +#include "util.hpp" namespace ESM { @@ -8,11 +11,24 @@ namespace ESM void Global::load (ESMReader &esm) { + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + mValue.read (esm, ESM::Variant::Format_Global); } void Global::save (ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } + mValue.write (esm, ESM::Variant::Format_Global); } diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index cc5dbbdcf..45b47a3bc 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -24,6 +24,8 @@ struct Global std::string mId; Variant mValue; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; From c4fd4be3ea756c38c8e5c18900c5e1fb2fbf4bbe Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Wed, 8 Jul 2015 21:17:59 +0300 Subject: [PATCH 271/441] Add NAME handling to GameSetting record (cherry picked from commit b2f3ccb080b459f212280cc2565110a0343645ec) --- components/esm/loadgmst.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index 21d66339a..9e2a80270 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -1,5 +1,7 @@ #include "loadgmst.hpp" +#include "esmreader.hpp" +#include "esmwriter.hpp" #include "defs.hpp" namespace ESM @@ -8,11 +10,13 @@ namespace ESM void GameSetting::load (ESMReader &esm) { + mId = esm.getHNString("NAME"); mValue.read (esm, ESM::Variant::Format_Gmst); } void GameSetting::save (ESMWriter &esm) const { + esm.writeHNCString("NAME", mId); mValue.write (esm, ESM::Variant::Format_Gmst); } From e0d5208d26c3bee5fa7dc909134d22613050809f Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Wed, 8 Jul 2015 21:24:05 +0300 Subject: [PATCH 272/441] Remove explicit record ID in load/read methods of MWWorld::Store (cherry picked from commit 89e44c8f1fcb02a0ea78b468402a2c8e96d5465c) --- apps/openmw/mwworld/store.cpp | 10 ++-- apps/openmw/mwworld/store.hpp | 110 +++++++++++++++++++++++----------- components/esm/util.cpp | 18 ++++++ components/esm/util.hpp | 27 ++++++++- 4 files changed, 123 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index cdcc00b4d..767a33b13 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -35,7 +35,7 @@ void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) } } -void Store::load(ESM::ESMReader &esm, const std::string &id) +void Store::load(ESM::ESMReader &esm) { // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, // and we merge all this data into one Cell object. However, we can't simply search for the cell id, @@ -43,9 +43,9 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) // are not available until both cells have been loaded at least partially! // All cells have a name record, even nameless exterior cells. - std::string idLower = Misc::StringUtils::lowerCase(id); ESM::Cell cell; - cell.mName = id; + cell.loadName(esm); + std::string idLower = Misc::StringUtils::lowerCase(cell.mName); // Load the (x,y) coordinates of the cell, if it is an exterior cell, // so we can find the cell we need to merge with @@ -119,9 +119,9 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) } } -void Store::load(ESM::ESMReader &esm, const std::string &id) +void Store::load(ESM::ESMReader &esm) { - load(esm, id, esm.getIndex()); + load(esm, esm.getIndex()); } } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index ab09782b1..a62e657cf 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -10,6 +10,7 @@ #include #include +#include #include @@ -26,15 +27,19 @@ namespace MWWorld virtual size_t getSize() const = 0; virtual int getDynamicSize() const { return 0; } - virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; + virtual void load(ESM::ESMReader &esm) = 0; virtual bool eraseStatic(const std::string &id) {return false;} virtual void clearDynamic() {} virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - virtual void read (ESM::ESMReader& reader, const std::string& id) {} + virtual void read (ESM::ESMReader& reader) {} ///< Read into dynamic storage + + virtual std::string getLastAddedRecordId() const { return ""; } + ///< Returns the last loaded/read ID or empty string if a loaded record has no ID + virtual bool isLastAddedRecordDeleted() const { return false; } }; template @@ -126,6 +131,7 @@ namespace MWWorld } }; + T mLastAddedRecord; friend class ESMStore; @@ -208,15 +214,16 @@ namespace MWWorld return ptr; } - void load(ESM::ESMReader &esm, const std::string &id) { - std::string idLower = Misc::StringUtils::lowerCase(id); + void load(ESM::ESMReader &esm) { + T record; + record.load(esm); - std::pair inserted = mStatic.insert(std::make_pair(idLower, T())); + std::string idLower = Misc::StringUtils::lowerCase(record.mId); + std::pair inserted = mStatic.insert(std::make_pair(idLower, record)); if (inserted.second) mShared.push_back(&inserted.first->second); - inserted.first->second.mId = idLower; - inserted.first->second.load(esm); + mLastAddedRecord = record; } void setUp() { @@ -325,58 +332,79 @@ namespace MWWorld ++iter) { writer.startRecord (T::sRecordId); - writer.writeHNString ("NAME", iter->second.mId); iter->second.save (writer); writer.endRecord (T::sRecordId); } } - void read (ESM::ESMReader& reader, const std::string& id) + void read (ESM::ESMReader& reader) { T record; - record.mId = id; record.load (reader); insert (record); + + mLastAddedRecord = record; + } + + std::string getLastAddedRecordId() const + { + return ESM::getRecordId(mLastAddedRecord); + } + + bool isLastAddedRecordDeleted() const + { + return ESM::isRecordDeleted(mLastAddedRecord); } }; template <> - inline void Store::load(ESM::ESMReader &esm, const std::string &id) { - std::string idLower = Misc::StringUtils::lowerCase(id); + inline void Store::load(ESM::ESMReader &esm) { + ESM::Dialogue dialogue; + dialogue.load(esm); - std::map::iterator it = mStatic.find(idLower); - if (it == mStatic.end()) { - it = mStatic.insert( std::make_pair( idLower, ESM::Dialogue() ) ).first; - it->second.mId = id; // don't smash case here, as this line is printed + std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId); + std::map::iterator found = mStatic.find(idLower); + if (found == mStatic.end()) + { + mStatic.insert(std::make_pair(idLower, dialogue)); } - - it->second.load(esm); + else + { + found->second = dialogue; + } + + mLastAddedRecord = dialogue; } template <> - inline void Store::load(ESM::ESMReader &esm, const std::string &id) { - ESM::Script scpt; - scpt.load(esm); - Misc::StringUtils::toLower(scpt.mId); + inline void Store::load(ESM::ESMReader &esm) { + ESM::Script script; + script.load(esm); - std::pair inserted = mStatic.insert(std::make_pair(scpt.mId, scpt)); + std::string idLower = Misc::StringUtils::toLower(script.mId); + std::pair inserted = mStatic.insert(std::make_pair(idLower, script)); if (inserted.second) mShared.push_back(&inserted.first->second); else - inserted.first->second = scpt; + inserted.first->second = script; + + mLastAddedRecord = script; } template <> - inline void Store::load(ESM::ESMReader &esm, const std::string &id) + inline void Store::load(ESM::ESMReader &esm) { - ESM::StartScript s; - s.load(esm); - s.mId = Misc::StringUtils::toLower(s.mId); - std::pair inserted = mStatic.insert(std::make_pair(s.mId, s)); + ESM::StartScript script; + script.load(esm); + + std::string idLower = Misc::StringUtils::toLower(script.mId); + std::pair inserted = mStatic.insert(std::make_pair(idLower, script)); if (inserted.second) mShared.push_back(&inserted.first->second); else - inserted.first->second = s; + inserted.first->second = script; + + mLastAddedRecord = script; } template <> @@ -385,6 +413,7 @@ namespace MWWorld // For multiple ESM/ESP files we need one list per file. typedef std::vector LandTextureList; std::vector mStatic; + ESM::LandTexture mLastLoadedTexture; public: Store() { @@ -426,10 +455,9 @@ namespace MWWorld return mStatic[plugin].size(); } - void load(ESM::ESMReader &esm, const std::string &id, size_t plugin) { + void load(ESM::ESMReader &esm, size_t plugin) { ESM::LandTexture lt; lt.load(esm); - lt.mId = id; // Make sure we have room for the structure if (plugin >= mStatic.size()) { @@ -443,7 +471,7 @@ namespace MWWorld ltexl[lt.mIndex] = lt; } - void load(ESM::ESMReader &esm, const std::string &id); + void load(ESM::ESMReader &esm); iterator begin(size_t plugin) const { assert(plugin < mStatic.size()); @@ -454,6 +482,16 @@ namespace MWWorld assert(plugin < mStatic.size()); return mStatic[plugin].end(); } + + std::string getLastAddedRecordId() const + { + return ESM::getRecordId(mLastLoadedTexture); + } + + bool isLastAddedRecordDeleted() const + { + return ESM::isRecordDeleted(mLastLoadedTexture); + } }; template <> @@ -521,7 +559,7 @@ namespace MWWorld return ptr; } - void load(ESM::ESMReader &esm, const std::string &id) { + void load(ESM::ESMReader &esm) { ESM::Land *ptr = new ESM::Land(); ptr->load(esm); @@ -688,7 +726,7 @@ namespace MWWorld // errors related to the compare operator used in std::find for ESM::MovedCellRefTracker::find. // There some nasty three-way cyclic header dependency involved, which I could only fix by moving // this method. - void load(ESM::ESMReader &esm, const std::string &id); + void load(ESM::ESMReader &esm); iterator intBegin() const { return iterator(mSharedInt.begin()); @@ -857,7 +895,7 @@ namespace MWWorld mCells = &cells; } - void load(ESM::ESMReader &esm, const std::string &id) { + void load(ESM::ESMReader &esm) { ESM::Pathgrid pathgrid; pathgrid.load(esm); diff --git a/components/esm/util.cpp b/components/esm/util.cpp index ff2436544..4cfe644e8 100644 --- a/components/esm/util.cpp +++ b/components/esm/util.cpp @@ -18,4 +18,22 @@ namespace ESM { esm.writeHNT("DELE", static_cast(0)); } + + template <> + bool isRecordDeleted(const StartScript &script) + { + return false; + } + + template <> + bool isRecordDeleted(const Race &race) + { + return false; + } + + template <> + bool isRecordDeleted(const GameSetting &gmst) + { + return false; + } } diff --git a/components/esm/util.hpp b/components/esm/util.hpp index 61cb13892..258372e31 100644 --- a/components/esm/util.hpp +++ b/components/esm/util.hpp @@ -8,7 +8,10 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "loadbsgn.hpp" +#include "loadsscr.hpp" +#include "loadglob.hpp" +#include "loadrace.hpp" +#include "loadgmst.hpp" namespace ESM { @@ -55,6 +58,28 @@ struct Vector3 bool readDeleSubRecord(ESMReader &esm); void writeDeleSubRecord(ESMWriter &esm); +template +std::string getRecordId(const RecordT &record) +{ + return record.mId; +} + +template +bool isRecordDeleted(const RecordT &record) +{ + return record.mIsDeleted; +} + +// The following records can't be deleted (for now) +template <> +bool isRecordDeleted(const StartScript &script); + +template <> +bool isRecordDeleted(const Race &race); + +template <> +bool isRecordDeleted(const GameSetting &gmst); + } #endif From 2564eb9841d2cd999f6ff2a545581cdc9c7af7de Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Wed, 8 Jul 2015 21:26:20 +0300 Subject: [PATCH 273/441] Remove NAME handling from MWWorld::ESMStore (cherry picked from commit 9301bc148e9fa10e58519debdc7103a9babae07b) --- apps/openmw/mwworld/esmstore.cpp | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 5d9beecb6..50a7e3376 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -95,23 +95,12 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) throw std::runtime_error(error.str()); } } else { - // Load it - std::string id = esm.getHNOString("NAME"); - // ... unless it got deleted! This means that the following record - // has been deleted, and trying to load it using standard assumptions - // on the structure will (probably) fail. - if (esm.isNextSub("DELE")) { - esm.skipRecord(); - it->second->eraseStatic(id); - continue; - } - it->second->load(esm, id); - - // DELE can also occur after the usual subrecords - if (esm.isNextSub("DELE")) { - esm.skipRecord(); - it->second->eraseStatic(id); - continue; + it->second->load(esm); + std::string id = it->second->getLastAddedRecordId(); + if (it->second->isLastAddedRecordDeleted()) + { + it->second->eraseStatic(id); + continue; } if (n.val==ESM::REC_DIAL) { @@ -194,13 +183,13 @@ void ESMStore::setUp() case ESM::REC_LEVC: { - std::string id = reader.getHNString ("NAME"); - mStores[type]->read (reader, id); + StoreBase *store = mStores[type]; + store->read (reader); // FIXME: there might be stale dynamic IDs in mIds from an earlier savegame // that really should be cleared instead of just overwritten - mIds[id] = type; + mIds[store->getLastAddedRecordId()] = type; } if (type==ESM::REC_NPC_) From f8b0cfc637c9573104c8b773652f6852977b1d0f Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Wed, 8 Jul 2015 21:40:01 +0300 Subject: [PATCH 274/441] Remove NAME handling from MWWorld::Globals (cherry picked from commit 897a52cdda1cec48833af47c2798b6836c9f4d36) --- apps/openmw/mwworld/globals.cpp | 26 ++++++++++++-------------- apps/openmw/mwworld/globals.hpp | 4 ++-- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index dcd7924a2..ed6eb821b 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -13,7 +13,7 @@ namespace MWWorld { Globals::Collection::const_iterator Globals::find (const std::string& name) const { - Collection::const_iterator iter = mVariables.find (name); + Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + name); @@ -23,7 +23,7 @@ namespace MWWorld Globals::Collection::iterator Globals::find (const std::string& name) { - Collection::iterator iter = mVariables.find (name); + Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + name); @@ -40,28 +40,28 @@ namespace MWWorld for (MWWorld::Store::iterator iter = globals.begin(); iter!=globals.end(); ++iter) { - mVariables.insert (std::make_pair (iter->mId, iter->mValue)); + mVariables.insert (std::make_pair (Misc::StringUtils::lowerCase (iter->mId), *iter)); } } const ESM::Variant& Globals::operator[] (const std::string& name) const { - return find (name)->second; + return find (Misc::StringUtils::lowerCase (name))->second.mValue; } ESM::Variant& Globals::operator[] (const std::string& name) { - return find (name)->second; + return find (Misc::StringUtils::lowerCase (name))->second.mValue; } char Globals::getType (const std::string& name) const { - Collection::const_iterator iter = mVariables.find (name); + Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) return ' '; - switch (iter->second.getType()) + switch (iter->second.mValue.getType()) { case ESM::VT_Short: return 's'; case ESM::VT_Long: return 'l'; @@ -81,8 +81,7 @@ namespace MWWorld for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) { writer.startRecord (ESM::REC_GLOB); - writer.writeHNString ("NAME", iter->first); - iter->second.write (writer, ESM::Variant::Format_Global); + iter->second.save (writer); writer.endRecord (ESM::REC_GLOB); } } @@ -91,14 +90,13 @@ namespace MWWorld { if (type==ESM::REC_GLOB) { - std::string id = reader.getHNString ("NAME"); + ESM::Global global; + global.load(reader); - Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (id)); + Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (global.mId)); if (iter!=mVariables.end()) - iter->second.read (reader, ESM::Variant::Format_Global); - else - reader.skipRecord(); + iter->second = global; return true; } diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index bb4ab13d9..3468c2e71 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include namespace ESM { @@ -29,7 +29,7 @@ namespace MWWorld { private: - typedef std::map Collection; + typedef std::map Collection; Collection mVariables; // type, value From a46ea70d65797f08a9c471650c897d8d285c800d Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 9 Jul 2015 15:17:38 +0300 Subject: [PATCH 275/441] Convert IDs of loaded records to lower case in MWWorld::Store (cherry picked from commit 00bf87b5611d7dbc8fc8e7d5add1a4a236071733) --- apps/openmw/mwworld/store.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index a62e657cf..d3fca9370 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -217,9 +217,9 @@ namespace MWWorld void load(ESM::ESMReader &esm) { T record; record.load(esm); + Misc::StringUtils::toLower(record.mId); - std::string idLower = Misc::StringUtils::lowerCase(record.mId); - std::pair inserted = mStatic.insert(std::make_pair(idLower, record)); + std::pair inserted = mStatic.insert(std::make_pair(record.mId, record)); if (inserted.second) mShared.push_back(&inserted.first->second); @@ -359,6 +359,7 @@ namespace MWWorld template <> inline void Store::load(ESM::ESMReader &esm) { + // The original letter case of a dialogue ID is saved, because it's printed ESM::Dialogue dialogue; dialogue.load(esm); @@ -380,9 +381,9 @@ namespace MWWorld inline void Store::load(ESM::ESMReader &esm) { ESM::Script script; script.load(esm); + Misc::StringUtils::toLower(script.mId); - std::string idLower = Misc::StringUtils::toLower(script.mId); - std::pair inserted = mStatic.insert(std::make_pair(idLower, script)); + std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); if (inserted.second) mShared.push_back(&inserted.first->second); else @@ -396,9 +397,9 @@ namespace MWWorld { ESM::StartScript script; script.load(esm); + Misc::StringUtils::toLower(script.mId); - std::string idLower = Misc::StringUtils::toLower(script.mId); - std::pair inserted = mStatic.insert(std::make_pair(idLower, script)); + std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); if (inserted.second) mShared.push_back(&inserted.first->second); else From 19acd1fada2fa1836d7cf4a0329b0a0fcf29edd1 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 9 Jul 2015 21:10:57 +0300 Subject: [PATCH 276/441] Letter case fix for MWWorld::Globals (cherry picked from commit 20723581a102f021ba04296096d5cd0c715f0d66) --- apps/openmw/mwworld/globals.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index ed6eb821b..48b88cd13 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -92,9 +92,9 @@ namespace MWWorld { ESM::Global global; global.load(reader); + Misc::StringUtils::toLower(global.mId); - Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (global.mId)); - + Collection::iterator iter = mVariables.find (global.mId); if (iter!=mVariables.end()) iter->second = global; From 80074f90bff0ca887a5e667032a09bde21532681 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 9 Jul 2015 21:45:24 +0300 Subject: [PATCH 277/441] Set Deleted flag to false when initializing ESM records (cherry picked from commit 7ecb54a776a298bee470397c40ce64945a2b0174) --- components/esm/loadacti.cpp | 4 + components/esm/loadacti.hpp | 2 + components/esm/loadalch.cpp | 4 + components/esm/loadalch.hpp | 2 + components/esm/loadappa.cpp | 96 ++++++----- components/esm/loadappa.hpp | 2 + components/esm/loadarmo.cpp | 4 + components/esm/loadarmo.hpp | 2 + components/esm/loadbody.cpp | 80 ++++----- components/esm/loadbody.hpp | 2 + components/esm/loadbook.cpp | 4 + components/esm/loadbook.hpp | 2 + components/esm/loadbsgn.cpp | 76 +++++---- components/esm/loadbsgn.hpp | 2 + components/esm/loadcell.hpp | 3 +- components/esm/loadclas.cpp | 3 + components/esm/loadclas.hpp | 2 + components/esm/loadclot.cpp | 4 + components/esm/loadclot.hpp | 2 + components/esm/loadcont.cpp | 4 + components/esm/loadcont.hpp | 2 + components/esm/loadcrea.cpp | 4 + components/esm/loadcrea.hpp | 2 + components/esm/loaddial.cpp | 207 +++++++++++----------- components/esm/loaddial.hpp | 2 + components/esm/loaddoor.cpp | 4 + components/esm/loaddoor.hpp | 2 + components/esm/loadench.cpp | 76 +++++---- components/esm/loadench.hpp | 2 + components/esm/loadfact.cpp | 129 +++++++------- components/esm/loadfact.hpp | 2 + components/esm/loadglob.cpp | 4 + components/esm/loadglob.hpp | 2 + components/esm/loadinfo.cpp | 302 +++++++++++++++++---------------- components/esm/loadinfo.hpp | 2 + components/esm/loadingr.cpp | 4 + components/esm/loadingr.hpp | 2 + components/esm/loadlevlist.cpp | 3 + components/esm/loadlevlist.hpp | 2 + components/esm/loadligh.cpp | 4 + components/esm/loadligh.hpp | 2 + components/esm/loadlock.cpp | 4 + components/esm/loadlock.hpp | 2 + components/esm/loadltex.cpp | 47 ++--- components/esm/loadltex.hpp | 6 +- components/esm/loadmisc.cpp | 4 + components/esm/loadmisc.hpp | 2 + components/esm/loadnpc.cpp | 4 + components/esm/loadnpc.hpp | 2 + components/esm/loadprob.cpp | 4 + components/esm/loadprob.hpp | 2 + components/esm/loadregn.cpp | 101 +++++------ components/esm/loadregn.hpp | 2 + components/esm/loadrepa.cpp | 96 ++++++----- components/esm/loadrepa.hpp | 2 + components/esm/loadscpt.cpp | 5 +- components/esm/loadscpt.hpp | 2 + components/esm/loadsndg.cpp | 4 + components/esm/loadsndg.hpp | 2 + components/esm/loadsoun.cpp | 4 + components/esm/loadsoun.hpp | 2 + components/esm/loadspel.cpp | 4 + components/esm/loadspel.hpp | 2 + components/esm/loadstat.cpp | 4 + components/esm/loadstat.hpp | 2 + components/esm/loadweap.cpp | 4 + components/esm/loadweap.hpp | 2 + 67 files changed, 785 insertions(+), 589 deletions(-) diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index 295d35f7e..14a3abe54 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Activator::sRecordId = REC_ACTI; + Activator::Activator() + : mIsDeleted(false) + {} + void Activator::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp index 406512a22..93de07b25 100644 --- a/components/esm/loadacti.hpp +++ b/components/esm/loadacti.hpp @@ -19,6 +19,8 @@ struct Activator bool mIsDeleted; + Activator(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index 12078a5f7..5faeb99e1 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Potion::sRecordId = REC_ALCH; + Potion::Potion() + : mIsDeleted(false) + {} + void Potion::load(ESMReader &esm) { mEffects.mList.clear(); diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index 3d1e6dee7..921585a9d 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -35,6 +35,8 @@ struct Potion bool mIsDeleted; + Potion(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index 9107e5a8e..ea375aa7f 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -9,60 +9,64 @@ namespace ESM { unsigned int Apparatus::sRecordId = REC_APPA; -void Apparatus::load(ESMReader &esm) -{ - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + Apparatus::Apparatus() + : mIsDeleted(false) + {} - bool hasData = false; - while (esm.hasMoreSubs()) + void Apparatus::load(ESMReader &esm) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'A','A','D','T'>::value: - esm.getHT(mData); - hasData = true; - break; - case ESM::FourCC<'S','C','R','I'>::value: - mScript = esm.getHString(); - break; - case ESM::FourCC<'I','T','E','X'>::value: - mIcon = esm.getHString(); - break; - default: - esm.fail("Unknown subrecord"); + return; } + + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'A','A','D','T'>::value: + esm.getHT(mData); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing AADT"); } - if (!hasData) - esm.fail("Missing AADT"); -} -void Apparatus::save(ESMWriter &esm) const -{ - esm.writeHNCString("NAME", mId); - if (mIsDeleted) + void Apparatus::save(ESMWriter &esm) const { - writeDeleSubRecord(esm); - return; - } + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } - esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); - esm.writeHNT("AADT", mData, 16); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNCString("ITEX", mIcon); -} + esm.writeHNCString("MODL", mModel); + esm.writeHNCString("FNAM", mName); + esm.writeHNT("AADT", mData, 16); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNCString("ITEX", mIcon); + } void Apparatus::blank() { diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp index 62d8561a1..2dac37995 100644 --- a/components/esm/loadappa.hpp +++ b/components/esm/loadappa.hpp @@ -40,6 +40,8 @@ struct Apparatus bool mIsDeleted; + Apparatus(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index 626893d00..d23a71cac 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -39,6 +39,10 @@ namespace ESM unsigned int Armor::sRecordId = REC_ARMO; + Armor::Armor() + : mIsDeleted(false) + {} + void Armor::load(ESMReader &esm) { mParts.mParts.clear(); diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index eb911254f..4ebe181a7 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -98,6 +98,8 @@ struct Armor bool mIsDeleted; + Armor(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index 644eb3b50..e0ebfd539 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -9,53 +9,57 @@ namespace ESM { unsigned int BodyPart::sRecordId = REC_BODY; + BodyPart::BodyPart() + : mIsDeleted(false) + {} -void BodyPart::load(ESMReader &esm) -{ - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) + void BodyPart::load(ESMReader &esm) { - return; - } + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } - bool hasData = false; - while (esm.hasMoreSubs()) - { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mRace = esm.getHString(); - break; - case ESM::FourCC<'B','Y','D','T'>::value: - esm.getHT(mData, 4); - hasData = true; - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mRace = esm.getHString(); + break; + case ESM::FourCC<'B','Y','D','T'>::value: + esm.getHT(mData, 4); + hasData = true; + break; + default: + esm.fail("Unknown subrecord"); + } } + + if (!hasData) + esm.fail("Missing BYDT subrecord"); } - if (!hasData) - esm.fail("Missing BYDT subrecord"); -} -void BodyPart::save(ESMWriter &esm) const -{ - esm.writeHNCString("NAME", mId); - if (mIsDeleted) + void BodyPart::save(ESMWriter &esm) const { - writeDeleSubRecord(esm); - return; - } + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mRace); - esm.writeHNT("BYDT", mData, 4); -} + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mRace); + esm.writeHNT("BYDT", mData, 4); + } void BodyPart::blank() { diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index 8e1e81f82..f32d2fb58 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -62,6 +62,8 @@ struct BodyPart bool mIsDeleted; + BodyPart(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index aec361873..2824b6200 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Book::sRecordId = REC_BOOK; + Book::Book() + : mIsDeleted(false) + {} + void Book::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp index 2f374c4b2..931f813c0 100644 --- a/components/esm/loadbook.hpp +++ b/components/esm/loadbook.hpp @@ -30,6 +30,8 @@ struct Book bool mIsDeleted; + Book(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 7892bfc68..8cdeed3f6 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -9,50 +9,54 @@ namespace ESM { unsigned int BirthSign::sRecordId = REC_BSGN; -void BirthSign::load(ESMReader &esm) -{ - mPowers.mList.clear(); - - mIsDeleted = readDeleSubRecord(esm); - mId = esm.getHNString("NAME"); + BirthSign::BirthSign() + : mIsDeleted(false) + {} - while (esm.hasMoreSubs()) + void BirthSign::load(ESMReader &esm) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + mPowers.mList.clear(); + + mIsDeleted = readDeleSubRecord(esm); + mId = esm.getHNString("NAME"); + + while (esm.hasMoreSubs()) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'T','N','A','M'>::value: - mTexture = esm.getHString(); - break; - case ESM::FourCC<'D','E','S','C'>::value: - mDescription = esm.getHString(); - break; - case ESM::FourCC<'N','P','C','S'>::value: - mPowers.add(esm); - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'T','N','A','M'>::value: + mTexture = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mPowers.add(esm); + break; + default: + esm.fail("Unknown subrecord"); + } } } -} -void BirthSign::save(ESMWriter &esm) const -{ - if (mIsDeleted) + void BirthSign::save(ESMWriter &esm) const { - writeDeleSubRecord(esm); - } - esm.writeHNCString("NAME", mId); - esm.writeHNOCString("FNAM", mName); - esm.writeHNOCString("TNAM", mTexture); - esm.writeHNOCString("DESC", mDescription); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + } + esm.writeHNCString("NAME", mId); + esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("TNAM", mTexture); + esm.writeHNOCString("DESC", mDescription); - mPowers.save(esm); -} + mPowers.save(esm); + } void BirthSign::blank() { diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index 41c70fb24..685ade82f 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -24,6 +24,8 @@ struct BirthSign bool mIsDeleted; + BirthSign(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index b313435d6..bf65c5fa8 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -85,7 +85,8 @@ struct Cell mWater(0), mWaterInt(false), mMapColor(0), - mRefNumCounter(0) + mRefNumCounter(0), + mIsDeleted(false) {} // Interior cells are indexed by this (it's the 'id'), for exterior diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index df47a1a33..1384a6280 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -23,6 +23,9 @@ namespace ESM "sSpecializationStealth" }; + Class::Class() + : mIsDeleted(false) + {} int& Class::CLDTstruct::getSkill (int index, bool major) { diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index b61913734..399e93c24 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -75,6 +75,8 @@ struct Class bool mIsDeleted; + Class(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 1c73c4f21..88f2e5715 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Clothing::sRecordId = REC_CLOT; + Clothing::Clothing() + : mIsDeleted(false) + {} + void Clothing::load(ESMReader &esm) { mParts.mParts.clear(); diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp index 8b4a8b3bb..202c1ec45 100644 --- a/components/esm/loadclot.hpp +++ b/components/esm/loadclot.hpp @@ -50,6 +50,8 @@ struct Clothing bool mIsDeleted; + Clothing(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index f54fe66e2..3d3d7fced 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -25,6 +25,10 @@ namespace ESM unsigned int Container::sRecordId = REC_CONT; + Container::Container() + : mIsDeleted(false) + {} + void Container::load(ESMReader &esm) { mInventory.mList.clear(); diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index 51166b8d4..31c7e1815 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -54,6 +54,8 @@ struct Container bool mIsDeleted; + Container(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index f1590f19b..1aead6c04 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Creature::sRecordId = REC_CREA; + Creature::Creature() + : mIsDeleted(false) + {} + void Creature::load(ESMReader &esm) { mPersistent = (esm.getRecordFlags() & 0x0400) != 0; diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index fb43f6272..22e834e45 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -98,6 +98,8 @@ struct Creature bool mIsDeleted; + Creature(); + const std::vector& getTransport() const; void load(ESMReader &esm); diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index 77a04ca01..667afc75c 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -13,125 +13,128 @@ namespace ESM { unsigned int Dialogue::sRecordId = REC_DIAL; -void Dialogue::load(ESMReader &esm) -{ - mIsDeleted = false; - - mId = esm.getHNString("NAME"); - esm.getSubNameIs("DATA"); - esm.getSubHeader(); - int si = esm.getSubSize(); - if (si == 1) - esm.getT(mType); - else if (si == 4) // The dialogue is deleted - { - int32_t empty; - esm.getT(empty); // Skip an empty DATA - mIsDeleted = readDeleSubRecord(esm); - } - else - esm.fail("Unknown sub record size"); -} + Dialogue::Dialogue() + : mIsDeleted(false) + {} -void Dialogue::save(ESMWriter &esm) const -{ - esm.writeHNCString("NAME", mId); - if (mIsDeleted) + void Dialogue::load(ESMReader &esm) { - esm.writeHNT("DATA", static_cast(0)); - writeDeleSubRecord(esm); - } - else - { - esm.writeHNT("DATA", mType); - } -} - -void Dialogue::blank() -{ - mInfo.clear(); -} - -void Dialogue::readInfo(ESMReader &esm, bool merge) -{ - const std::string& id = esm.getHNOString("INAM"); - - if (!merge || mInfo.empty()) - { - ESM::DialInfo info; - info.mId = id; - info.load(esm); - mLookup[id] = mInfo.insert(mInfo.end(), info); - return; + mIsDeleted = false; + + mId = esm.getHNString("NAME"); + esm.getSubNameIs("DATA"); + esm.getSubHeader(); + int si = esm.getSubSize(); + if (si == 1) + esm.getT(mType); + else if (si == 4) // The dialogue is deleted + { + int32_t empty; + esm.getT(empty); // Skip an empty DATA + mIsDeleted = readDeleSubRecord(esm); + } + else + esm.fail("Unknown sub record size"); } - ESM::Dialogue::InfoContainer::iterator it = mInfo.end(); - - std::map::iterator lookup; - - lookup = mLookup.find(id); - - ESM::DialInfo info; - if (lookup != mLookup.end()) - { - it = lookup->second; - - // Merge with existing record. Only the subrecords that are present in - // the new record will be overwritten. - it->load(esm); - info = *it; - - // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record - mInfo.erase(it); - mLookup.erase(lookup); - } - else + void Dialogue::save(ESMWriter &esm) const { - info.mId = id; - info.load(esm); + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + esm.writeHNT("DATA", static_cast(0)); + writeDeleSubRecord(esm); + } + else + { + esm.writeHNT("DATA", mType); + } } - if (info.mNext.empty()) + void Dialogue::blank() { - mLookup[id] = mInfo.insert(mInfo.end(), info); - return; - } - if (info.mPrev.empty()) - { - mLookup[id] = mInfo.insert(mInfo.begin(), info); - return; + mInfo.clear(); } - lookup = mLookup.find(info.mPrev); - if (lookup != mLookup.end()) + void Dialogue::readInfo(ESMReader &esm, bool merge) { - it = lookup->second; + const std::string& id = esm.getHNOString("INAM"); - mLookup[id] = mInfo.insert(++it, info); - return; - } + if (!merge || mInfo.empty()) + { + ESM::DialInfo info; + info.mId = id; + info.load(esm); + mLookup[id] = mInfo.insert(mInfo.end(), info); + return; + } - lookup = mLookup.find(info.mNext); - if (lookup != mLookup.end()) - { - it = lookup->second; + ESM::Dialogue::InfoContainer::iterator it = mInfo.end(); - mLookup[id] = mInfo.insert(it, info); - return; - } + std::map::iterator lookup; - std::cerr << "Failed to insert info " << id << std::endl; -} + lookup = mLookup.find(id); -void Dialogue::clearDeletedInfos() -{ - for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); ) - { - if (it->mQuestStatus == DialInfo::QS_Deleted) - it = mInfo.erase(it); + ESM::DialInfo info; + if (lookup != mLookup.end()) + { + it = lookup->second; + + // Merge with existing record. Only the subrecords that are present in + // the new record will be overwritten. + it->load(esm); + info = *it; + + // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record + mInfo.erase(it); + mLookup.erase(lookup); + } else - ++it; + { + info.mId = id; + info.load(esm); + } + + if (info.mNext.empty()) + { + mLookup[id] = mInfo.insert(mInfo.end(), info); + return; + } + if (info.mPrev.empty()) + { + mLookup[id] = mInfo.insert(mInfo.begin(), info); + return; + } + + lookup = mLookup.find(info.mPrev); + if (lookup != mLookup.end()) + { + it = lookup->second; + + mLookup[id] = mInfo.insert(++it, info); + return; + } + + lookup = mLookup.find(info.mNext); + if (lookup != mLookup.end()) + { + it = lookup->second; + + mLookup[id] = mInfo.insert(it, info); + return; + } + + std::cerr << "Failed to insert info " << id << std::endl; } -} + void Dialogue::clearDeletedInfos() + { + for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); ) + { + if (it->mQuestStatus == DialInfo::QS_Deleted) + it = mInfo.erase(it); + else + ++it; + } + } } diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index ab8cf4bff..5e7d09871 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -47,6 +47,8 @@ struct Dialogue bool mIsDeleted; + Dialogue(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index 6ee43fd1a..87382fa7b 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Door::sRecordId = REC_DOOR; + Door::Door() + : mIsDeleted(false) + {} + void Door::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index b0326a744..546471ed3 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -19,6 +19,8 @@ struct Door bool mIsDeleted; + Door(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 88d9e0f2e..1518e0385 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -9,51 +9,55 @@ namespace ESM { unsigned int Enchantment::sRecordId = REC_ENCH; -void Enchantment::load(ESMReader &esm) -{ - mEffects.mList.clear(); + Enchantment::Enchantment() + : mIsDeleted(false) + {} - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) + void Enchantment::load(ESMReader &esm) { - return; - } + mEffects.mList.clear(); - bool hasData = false; - while (esm.hasMoreSubs()) - { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) + { + return; + } + + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'E','N','D','T'>::value: - esm.getHT(mData, 16); - hasData = true; - break; - case ESM::FourCC<'E','N','A','M'>::value: - mEffects.add(esm); - break; - default: - esm.fail("Unknown subrecord"); - break; + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'E','N','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'E','N','A','M'>::value: + mEffects.add(esm); + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + if (!hasData) + esm.fail("Missing ENDT subrecord"); } - if (!hasData) - esm.fail("Missing ENDT subrecord"); -} -void Enchantment::save(ESMWriter &esm) const -{ - esm.writeHNCString("NAME", mId); - if (mIsDeleted) + void Enchantment::save(ESMWriter &esm) const { - writeDeleSubRecord(esm); - return; - } + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } - esm.writeHNT("ENDT", mData, 16); - mEffects.save(esm); -} + esm.writeHNT("ENDT", mData, 16); + mEffects.save(esm); + } void Enchantment::blank() { diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index c9b19e31b..6ebe8e940 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -44,6 +44,8 @@ struct Enchantment bool mIsDeleted; + Enchantment(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 9149c2a30..53f3aa5a6 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -11,6 +11,10 @@ namespace ESM { unsigned int Faction::sRecordId = REC_FACT; + Faction::Faction() + : mIsDeleted(false) + {} + int& Faction::FADTstruct::getSkill (int index, bool ignored) { if (index<0 || index>=7) @@ -27,82 +31,83 @@ namespace ESM return mSkills[index]; } -void Faction::load(ESMReader &esm) -{ - mReactions.clear(); - for (int i=0;i<10;++i) - mRanks[i].clear(); - - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) + void Faction::load(ESMReader &esm) { - return; - } + mReactions.clear(); + for (int i=0;i<10;++i) + mRanks[i].clear(); - int rankCounter=0; - bool hasData = false; - while (esm.hasMoreSubs()) - { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','N','A','M'>::value: - if (rankCounter >= 10) - esm.fail("Rank out of range"); - mRanks[rankCounter++] = esm.getHString(); - break; - case ESM::FourCC<'F','A','D','T'>::value: - esm.getHT(mData, 240); - if (mData.mIsHidden > 1) - esm.fail("Unknown flag!"); - hasData = true; - break; - case ESM::FourCC<'A','N','A','M'>::value: + return; + } + + int rankCounter=0; + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) { - std::string faction = esm.getHString(); - int reaction; - esm.getHNT(reaction, "INTV"); - mReactions[faction] = reaction; - break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','N','A','M'>::value: + if (rankCounter >= 10) + esm.fail("Rank out of range"); + mRanks[rankCounter++] = esm.getHString(); + break; + case ESM::FourCC<'F','A','D','T'>::value: + esm.getHT(mData, 240); + if (mData.mIsHidden > 1) + esm.fail("Unknown flag!"); + hasData = true; + break; + case ESM::FourCC<'A','N','A','M'>::value: + { + std::string faction = esm.getHString(); + int reaction; + esm.getHNT(reaction, "INTV"); + mReactions[faction] = reaction; + break; + } + default: + esm.fail("Unknown subrecord"); } - default: - esm.fail("Unknown subrecord"); } + if (!hasData) + esm.fail("Missing FADT subrecord"); } - if (!hasData) - esm.fail("Missing FADT subrecord"); -} -void Faction::save(ESMWriter &esm) const -{ - esm.writeHNCString("NAME", mId); - if (mIsDeleted) + + void Faction::save(ESMWriter &esm) const { - writeDeleSubRecord(esm); - return; - } + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } - esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); - for (int i = 0; i < 10; i++) - { - if (mRanks[i].empty()) - break; + for (int i = 0; i < 10; i++) + { + if (mRanks[i].empty()) + break; - esm.writeHNString("RNAM", mRanks[i], 32); - } + esm.writeHNString("RNAM", mRanks[i], 32); + } - esm.writeHNT("FADT", mData, 240); + esm.writeHNT("FADT", mData, 240); - for (std::map::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) - { - esm.writeHNString("ANAM", it->first); - esm.writeHNT("INTV", it->second); + for (std::map::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) + { + esm.writeHNString("ANAM", it->first); + esm.writeHNT("INTV", it->second); + } } -} void Faction::blank() { diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index fec13b1ca..96c02028b 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -64,6 +64,8 @@ struct Faction bool mIsDeleted; + Faction(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index 6cc37d675..392df02b5 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Global::sRecordId = REC_GLOB; + Global::Global() + : mIsDeleted(false) + {} + void Global::load (ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index 45b47a3bc..c0219c0ba 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -26,6 +26,8 @@ struct Global bool mIsDeleted; + Global(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 3b5e0ea3a..4c997213a 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -9,169 +9,173 @@ namespace ESM { unsigned int DialInfo::sRecordId = REC_INFO; -void DialInfo::load(ESMReader &esm) -{ - mQuestStatus = QS_None; - mFactionLess = false; - - mPrev = esm.getHNString("PNAM"); - mNext = esm.getHNString("NNAM"); + DialInfo::DialInfo() + : mIsDeleted(false) + {} - // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings - mSelects.clear(); - - // If the info is deleted, NAME and DELE sub-records are followed after NNAM - if (esm.isNextSub("NAME")) + void DialInfo::load(ESMReader &esm) { - mResponse = esm.getHString(); - mIsDeleted = readDeleSubRecord(esm); - return; - } - - esm.getSubNameIs("DATA"); - esm.getHT(mData, 12); - - if (!esm.hasMoreSubs()) - return; - - // What follows is somewhat spaghetti-ish, but it's worth if for - // an extra speedup. INFO is by far the most common record type. + mQuestStatus = QS_None; + mFactionLess = false; - // subName is a reference to the original, so it changes whenever - // a new sub name is read. esm.isEmptyOrGetName() will get the - // next name for us, or return true if there are no more records. - esm.getSubName(); - const NAME &subName = esm.retSubName(); + mPrev = esm.getHNString("PNAM"); + mNext = esm.getHNString("NNAM"); - if (subName.val == REC_ONAM) - { - mActor = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_RNAM) - { - mRace = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_CNAM) - { - mClass = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } + // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings + mSelects.clear(); - if (subName.val == REC_FNAM) - { - mFaction = esm.getHString(); - if (mFaction == "FFFF") - mFactionLess = true; - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_ANAM) - { - mCell = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_DNAM) - { - mPcFaction = esm.getHString(); - if (esm.isEmptyOrGetName()) + // If the info is deleted, NAME and DELE sub-records are followed after NNAM + if (esm.isNextSub("NAME")) + { + mResponse = esm.getHString(); + mIsDeleted = readDeleSubRecord(esm); return; - } - if (subName.val == REC_SNAM) - { - mSound = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_NAME) - { - mResponse = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } + } - while (subName.val == REC_SCVR) - { - SelectStruct ss; - - ss.mSelectRule = esm.getHString(); - - ss.mValue.read (esm, Variant::Format_Info); - - mSelects.push_back(ss); - - if (esm.isEmptyOrGetName()) - return; - } + esm.getSubNameIs("DATA"); + esm.getHT(mData, 12); - if (subName.val == REC_BNAM) - { - mResultScript = esm.getHString(); - if (esm.isEmptyOrGetName()) + if (!esm.hasMoreSubs()) return; - } - if (subName.val == REC_QSTN) - mQuestStatus = QS_Name; - else if (subName.val == REC_QSTF) - mQuestStatus = QS_Finished; - else if (subName.val == REC_QSTR) - mQuestStatus = QS_Restart; - else if (subName.val == REC_DELE) - mQuestStatus = QS_Deleted; - else - esm.fail( - "Don't know what to do with " + subName.toString() - + " in INFO " + mId); - - if (mQuestStatus != QS_None) - // Skip rest of record - esm.skipRecord(); -} - -void DialInfo::save(ESMWriter &esm) const -{ - esm.writeHNCString("PNAM", mPrev); - esm.writeHNCString("NNAM", mNext); - if (mIsDeleted) - { - esm.writeHNCString("NAME", mResponse); - writeDeleSubRecord(esm); - return; + // What follows is somewhat spaghetti-ish, but it's worth if for + // an extra speedup. INFO is by far the most common record type. + + // subName is a reference to the original, so it changes whenever + // a new sub name is read. esm.isEmptyOrGetName() will get the + // next name for us, or return true if there are no more records. + esm.getSubName(); + const NAME &subName = esm.retSubName(); + + if (subName.val == REC_ONAM) + { + mActor = esm.getHString(); + if (esm.isEmptyOrGetName()) + return; + } + if (subName.val == REC_RNAM) + { + mRace = esm.getHString(); + if (esm.isEmptyOrGetName()) + return; + } + if (subName.val == REC_CNAM) + { + mClass = esm.getHString(); + if (esm.isEmptyOrGetName()) + return; + } + + if (subName.val == REC_FNAM) + { + mFaction = esm.getHString(); + if (mFaction == "FFFF") + mFactionLess = true; + if (esm.isEmptyOrGetName()) + return; + } + if (subName.val == REC_ANAM) + { + mCell = esm.getHString(); + if (esm.isEmptyOrGetName()) + return; + } + if (subName.val == REC_DNAM) + { + mPcFaction = esm.getHString(); + if (esm.isEmptyOrGetName()) + return; + } + if (subName.val == REC_SNAM) + { + mSound = esm.getHString(); + if (esm.isEmptyOrGetName()) + return; + } + if (subName.val == REC_NAME) + { + mResponse = esm.getHString(); + if (esm.isEmptyOrGetName()) + return; + } + + while (subName.val == REC_SCVR) + { + SelectStruct ss; + + ss.mSelectRule = esm.getHString(); + + ss.mValue.read (esm, Variant::Format_Info); + + mSelects.push_back(ss); + + if (esm.isEmptyOrGetName()) + return; + } + + if (subName.val == REC_BNAM) + { + mResultScript = esm.getHString(); + if (esm.isEmptyOrGetName()) + return; + } + + if (subName.val == REC_QSTN) + mQuestStatus = QS_Name; + else if (subName.val == REC_QSTF) + mQuestStatus = QS_Finished; + else if (subName.val == REC_QSTR) + mQuestStatus = QS_Restart; + else if (subName.val == REC_DELE) + mQuestStatus = QS_Deleted; + else + esm.fail( + "Don't know what to do with " + subName.toString() + + " in INFO " + mId); + + if (mQuestStatus != QS_None) + // Skip rest of record + esm.skipRecord(); } - esm.writeHNT("DATA", mData, 12); - esm.writeHNOCString("ONAM", mActor); - esm.writeHNOCString("RNAM", mRace); - esm.writeHNOCString("CNAM", mClass); - esm.writeHNOCString("FNAM", mFaction); - esm.writeHNOCString("ANAM", mCell); - esm.writeHNOCString("DNAM", mPcFaction); - esm.writeHNOCString("SNAM", mSound); - esm.writeHNOString("NAME", mResponse); - - for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) + void DialInfo::save(ESMWriter &esm) const { - esm.writeHNString("SCVR", it->mSelectRule); - it->mValue.write (esm, Variant::Format_Info); - } - - esm.writeHNOString("BNAM", mResultScript); - - switch(mQuestStatus) - { - case QS_Name: esm.writeHNT("QSTN",'\1'); break; - case QS_Finished: esm.writeHNT("QSTF", '\1'); break; - case QS_Restart: esm.writeHNT("QSTR", '\1'); break; - case QS_Deleted: esm.writeHNT("DELE", '\1'); break; - default: break; + esm.writeHNCString("PNAM", mPrev); + esm.writeHNCString("NNAM", mNext); + if (mIsDeleted) + { + esm.writeHNCString("NAME", mResponse); + writeDeleSubRecord(esm); + return; + } + + esm.writeHNT("DATA", mData, 12); + esm.writeHNOCString("ONAM", mActor); + esm.writeHNOCString("RNAM", mRace); + esm.writeHNOCString("CNAM", mClass); + esm.writeHNOCString("FNAM", mFaction); + esm.writeHNOCString("ANAM", mCell); + esm.writeHNOCString("DNAM", mPcFaction); + esm.writeHNOCString("SNAM", mSound); + esm.writeHNOString("NAME", mResponse); + + for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) + { + esm.writeHNString("SCVR", it->mSelectRule); + it->mValue.write (esm, Variant::Format_Info); + } + + esm.writeHNOString("BNAM", mResultScript); + + switch(mQuestStatus) + { + case QS_Name: esm.writeHNT("QSTN",'\1'); break; + case QS_Finished: esm.writeHNT("QSTF", '\1'); break; + case QS_Restart: esm.writeHNT("QSTR", '\1'); break; + case QS_Deleted: esm.writeHNT("DELE", '\1'); break; + default: break; + } } -} void DialInfo::blank() { diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 8b4fae761..fbb7e36a5 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -108,6 +108,8 @@ struct DialInfo bool mIsDeleted; + DialInfo(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 8bf0278f7..a7018b36d 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Ingredient::sRecordId = REC_INGR; + Ingredient::Ingredient() + : mIsDeleted(false) + {} + void Ingredient::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index 69a650180..c92f28f18 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -33,6 +33,8 @@ struct Ingredient bool mIsDeleted; + Ingredient(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index c8cb110a6..1e07086bc 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -7,6 +7,9 @@ namespace ESM { + LevelledListBase::LevelledListBase() + : mIsDeleted(false) + {} void LevelledListBase::load(ESMReader &esm) { diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index 4165275b2..14ebc9937 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -38,6 +38,8 @@ struct LevelledListBase bool mIsDeleted; + LevelledListBase(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index bd91e096c..a153d500a 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Light::sRecordId = REC_LIGH; + Light::Light() + : mIsDeleted(false) + {} + void Light::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index 76274e6f4..d4d3418d8 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -49,6 +49,8 @@ struct Light bool mIsDeleted; + Light(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index c1e866b58..3b169af33 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Lockpick::sRecordId = REC_LOCK; + Lockpick::Lockpick() + : mIsDeleted(false) + {} + void Lockpick::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp index af18773ed..ce7de2c06 100644 --- a/components/esm/loadlock.hpp +++ b/components/esm/loadlock.hpp @@ -29,6 +29,8 @@ struct Lockpick bool mIsDeleted; + Lockpick(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index 0ee30b11c..13315e684 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -9,29 +9,32 @@ namespace ESM { unsigned int LandTexture::sRecordId = REC_LTEX; -void LandTexture::load(ESMReader &esm) -{ - mIsDeleted = readDeleSubRecord(esm); - mId = esm.getHNString("NAME"); - esm.getHNT(mIndex, "INTV"); - mTexture = esm.getHNString("DATA"); -} -void LandTexture::save(ESMWriter &esm) const -{ - if (mIsDeleted) + LandTexture::LandTexture() + : mIsDeleted(false) + {} + + void LandTexture::load(ESMReader &esm) { - writeDeleSubRecord(esm); + mIsDeleted = readDeleSubRecord(esm); + mId = esm.getHNString("NAME"); + esm.getHNT(mIndex, "INTV"); + mTexture = esm.getHNString("DATA"); + } + void LandTexture::save(ESMWriter &esm) const + { + if (mIsDeleted) + { + writeDeleSubRecord(esm); + } + esm.writeHNCString("NAME", mId); + esm.writeHNT("INTV", mIndex); + esm.writeHNCString("DATA", mTexture); } - esm.writeHNCString("NAME", mId); - esm.writeHNT("INTV", mIndex); - esm.writeHNCString("DATA", mTexture); -} - -void LandTexture::blank() -{ - mTexture.clear(); - mIndex = -1; - mIsDeleted = false; -} + void LandTexture::blank() + { + mTexture.clear(); + mIndex = -1; + mIsDeleted = false; + } } diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index e019e269e..33af77612 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -36,11 +36,13 @@ struct LandTexture bool mIsDeleted; - void blank(); - ///< Set record to default state (does not touch the ID). + LandTexture(); void load(ESMReader &esm); void save(ESMWriter &esm) const; + + void blank(); + ///< Set record to default state (does not touch the ID). }; } #endif diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index 11d2f2a05..08cbcf741 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Miscellaneous::sRecordId = REC_MISC; + Miscellaneous::Miscellaneous() + : mIsDeleted(false) + {} + void Miscellaneous::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadmisc.hpp b/components/esm/loadmisc.hpp index 5d5e9f66f..82018cd72 100644 --- a/components/esm/loadmisc.hpp +++ b/components/esm/loadmisc.hpp @@ -34,6 +34,8 @@ struct Miscellaneous bool mIsDeleted; + Miscellaneous(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 96957f1e2..1bdd5d483 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int NPC::sRecordId = REC_NPC_; + NPC::NPC() + : mIsDeleted(false) + {} + void NPC::load(ESMReader &esm) { mPersistent = (esm.getRecordFlags() & 0x0400) != 0; diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 7b75cb178..263752752 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -132,6 +132,8 @@ struct NPC bool mIsDeleted; + NPC(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index 12f7ad00a..f5287f986 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Probe::sRecordId = REC_PROB; + Probe::Probe() + : mIsDeleted(false) + {} + void Probe::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp index 9daab6b1b..748d498fc 100644 --- a/components/esm/loadprob.hpp +++ b/components/esm/loadprob.hpp @@ -29,6 +29,8 @@ struct Probe bool mIsDeleted; + Probe(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index b0adc6f58..2d99947b0 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -9,69 +9,74 @@ namespace ESM { unsigned int Region::sRecordId = REC_REGN; -void Region::load(ESMReader &esm) -{ - mIsDeleted = readDeleSubRecord(esm); - mId = esm.getHNString("NAME"); - mName = esm.getHNOString("FNAM"); + Region::Region() + : mIsDeleted(false) + {} - esm.getSubNameIs("WEAT"); - esm.getSubHeader(); - if (esm.getVer() == VER_12) - { - mData.mA = 0; - mData.mB = 0; - esm.getExact(&mData, sizeof(mData) - 2); - } - else if (esm.getVer() == VER_13) + void Region::load(ESMReader &esm) { - // May include the additional two bytes (but not necessarily) - if (esm.getSubSize() == sizeof(mData)) - esm.getExact(&mData, sizeof(mData)); - else + mIsDeleted = readDeleSubRecord(esm); + mId = esm.getHNString("NAME"); + mName = esm.getHNOString("FNAM"); + + esm.getSubNameIs("WEAT"); + esm.getSubHeader(); + if (esm.getVer() == VER_12) { mData.mA = 0; mData.mB = 0; - esm.getExact(&mData, sizeof(mData)-2); + esm.getExact(&mData, sizeof(mData) - 2); } - } - else - esm.fail("Don't know what to do in this version"); + else if (esm.getVer() == VER_13) + { + // May include the additional two bytes (but not necessarily) + if (esm.getSubSize() == sizeof(mData)) + esm.getExact(&mData, sizeof(mData)); + else + { + mData.mA = 0; + mData.mB = 0; + esm.getExact(&mData, sizeof(mData)-2); + } + } + else + esm.fail("Don't know what to do in this version"); - mSleepList = esm.getHNOString("BNAM"); + mSleepList = esm.getHNOString("BNAM"); - esm.getHNT(mMapColor, "CNAM"); + esm.getHNT(mMapColor, "CNAM"); - mSoundList.clear(); - while (esm.hasMoreSubs()) - { - SoundRef sr; - esm.getHNT(sr, "SNAM", 33); - mSoundList.push_back(sr); + mSoundList.clear(); + while (esm.hasMoreSubs()) + { + SoundRef sr; + esm.getHNT(sr, "SNAM", 33); + mSoundList.push_back(sr); + } } -} -void Region::save(ESMWriter &esm) const -{ - if (mIsDeleted) + + void Region::save(ESMWriter &esm) const { - writeDeleSubRecord(esm); - } - esm.writeHNString("NAME", mId); - esm.writeHNOCString("FNAM", mName); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + } + esm.writeHNString("NAME", mId); + esm.writeHNOCString("FNAM", mName); - if (esm.getVersion() == VER_12) - esm.writeHNT("WEAT", mData, sizeof(mData) - 2); - else - esm.writeHNT("WEAT", mData); + if (esm.getVersion() == VER_12) + esm.writeHNT("WEAT", mData, sizeof(mData) - 2); + else + esm.writeHNT("WEAT", mData); - esm.writeHNOCString("BNAM", mSleepList); + esm.writeHNOCString("BNAM", mSleepList); - esm.writeHNT("CNAM", mMapColor); - for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) - { - esm.writeHNT("SNAM", *it); + esm.writeHNT("CNAM", mMapColor); + for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) + { + esm.writeHNT("SNAM", *it); + } } -} void Region::blank() { diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index 921ab887b..9082437fe 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -53,6 +53,8 @@ struct Region bool mIsDeleted; + Region(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index 608536f02..fb213efd8 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -9,61 +9,65 @@ namespace ESM { unsigned int Repair::sRecordId = REC_REPA; -void Repair::load(ESMReader &esm) -{ - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + Repair::Repair() + : mIsDeleted(false) + {} - bool hasData = true; - while (esm.hasMoreSubs()) + void Repair::load(ESMReader &esm) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + mId = esm.getHNString("NAME"); + if (mIsDeleted = readDeleSubRecord(esm)) { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','I','D','T'>::value: - esm.getHT(mData, 16); - hasData = true; - break; - case ESM::FourCC<'S','C','R','I'>::value: - mScript = esm.getHString(); - break; - case ESM::FourCC<'I','T','E','X'>::value: - mIcon = esm.getHString(); - break; - default: - esm.fail("Unknown subrecord"); + return; } + + bool hasData = true; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','I','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing RIDT subrecord"); } - if (!hasData) - esm.fail("Missing RIDT subrecord"); -} -void Repair::save(ESMWriter &esm) const -{ - esm.writeHNCString("NAME", mId); - if (mIsDeleted) + void Repair::save(ESMWriter &esm) const { - writeDeleSubRecord(esm); - return; - } + esm.writeHNCString("NAME", mId); + if (mIsDeleted) + { + writeDeleSubRecord(esm); + return; + } - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); - esm.writeHNT("RIDT", mData, 16); - esm.writeHNOString("SCRI", mScript); - esm.writeHNOCString("ITEX", mIcon); -} + esm.writeHNT("RIDT", mData, 16); + esm.writeHNOString("SCRI", mScript); + esm.writeHNOCString("ITEX", mIcon); + } void Repair::blank() { diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp index a660574be..1448f9c77 100644 --- a/components/esm/loadrepa.hpp +++ b/components/esm/loadrepa.hpp @@ -29,6 +29,8 @@ struct Repair bool mIsDeleted; + Repair(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index b2b7309db..e6c516695 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -7,9 +7,12 @@ namespace ESM { - unsigned int Script::sRecordId = REC_SCPT; + Script::Script() + : mIsDeleted(false) + {} + void Script::loadSCVR(ESMReader &esm) { int s = mData.mStringTableSize; diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index 401dfe105..58b5842e8 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -52,6 +52,8 @@ public: bool mIsDeleted; + Script(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index f92c4eee8..261087be0 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int SoundGenerator::sRecordId = REC_SNDG; + SoundGenerator::SoundGenerator() + : mIsDeleted(false) + {} + void SoundGenerator::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index e486976f5..13eb18072 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -38,6 +38,8 @@ struct SoundGenerator bool mIsDeleted; + SoundGenerator(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 19e8a5d80..9a1a52b1e 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Sound::sRecordId = REC_SOUN; + Sound::Sound() + : mIsDeleted(false) + {} + void Sound::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index 95c89b29d..0b40ae751 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -25,6 +25,8 @@ struct Sound bool mIsDeleted; + Sound(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index cac06b200..d2d8c7d6d 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Spell::sRecordId = REC_SPEL; + Spell::Spell() + : mIsDeleted(false) + {} + void Spell::load(ESMReader &esm) { mEffects.mList.clear(); diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index 2aba131ad..327e94d8f 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -47,6 +47,8 @@ struct Spell bool mIsDeleted; + Spell(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index a49caba89..2fde46bd2 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Static::sRecordId = REC_STAT; + Static::Static() + : mIsDeleted(false) + {} + void Static::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index c4306ad8f..f88ad671b 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -30,6 +30,8 @@ struct Static bool mIsDeleted; + Static(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 66d65d0f5..38fb94adb 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -9,6 +9,10 @@ namespace ESM { unsigned int Weapon::sRecordId = REC_WEAP; + Weapon::Weapon() + : mIsDeleted(false) + {} + void Weapon::load(ESMReader &esm) { mId = esm.getHNString("NAME"); diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index 30dfbc92a..ce61eeb72 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -71,6 +71,8 @@ struct Weapon bool mIsDeleted; + Weapon(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; From 25a8cda9ac69d61eb9350c696a555ab6c6893ef0 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 11 Jul 2015 22:17:53 +0300 Subject: [PATCH 278/441] Some fixes for ESM Dialogues and Infos (cherry picked from commit e0983c815c32c9b79d340100ab2b4b6750820554) --- apps/openmw/mwworld/store.hpp | 3 ++- components/esm/loaddial.cpp | 2 ++ components/esm/loaddial.hpp | 3 ++- components/esm/loadinfo.cpp | 3 +++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index d3fca9370..27771f4ea 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -371,7 +371,8 @@ namespace MWWorld } else { - found->second = dialogue; + found->second.mIsDeleted = dialogue.mIsDeleted; + found->second.mType = dialogue.mType; } mLastAddedRecord = dialogue; diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index 667afc75c..aeec46872 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -32,6 +32,7 @@ namespace ESM int32_t empty; esm.getT(empty); // Skip an empty DATA mIsDeleted = readDeleSubRecord(esm); + mType = Unknown; } else esm.fail("Unknown sub record size"); @@ -54,6 +55,7 @@ namespace ESM void Dialogue::blank() { mInfo.clear(); + mIsDeleted = false; } void Dialogue::readInfo(ESMReader &esm, bool merge) diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 5e7d09871..8fc7e14e9 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -30,7 +30,8 @@ struct Dialogue Voice = 1, Greeting = 2, Persuasion = 3, - Journal = 4 + Journal = 4, + Unknown = -1 // Used for deleted dialogues }; std::string mId; diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 4c997213a..c1b12e24c 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -17,6 +17,7 @@ namespace ESM { mQuestStatus = QS_None; mFactionLess = false; + mIsDeleted = false; mPrev = esm.getHNString("PNAM"); mNext = esm.getHNString("NNAM"); @@ -142,6 +143,7 @@ namespace ESM { esm.writeHNCString("PNAM", mPrev); esm.writeHNCString("NNAM", mNext); + if (mIsDeleted) { esm.writeHNCString("NAME", mResponse); @@ -200,5 +202,6 @@ namespace ESM mResultScript.clear(); mFactionLess = false; mQuestStatus = QS_None; + mIsDeleted = false; } } From 2448aa05cb56798f41b8223c105c52a15cba97fa Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 12 Jul 2015 00:19:04 +0300 Subject: [PATCH 279/441] Add removing of deleted Infos to Dialogue::clearDeletedInfos() (cherry picked from commit adec0cb61df161e4bb25d0387d7a1ecde21363cb) --- components/esm/loaddial.cpp | 2 +- components/esm/loaddial.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index aeec46872..dfac0ce63 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -133,7 +133,7 @@ namespace ESM { for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); ) { - if (it->mQuestStatus == DialInfo::QS_Deleted) + if (it->mIsDeleted || it->mQuestStatus == DialInfo::QS_Deleted) it = mInfo.erase(it); else ++it; diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 8fc7e14e9..e80a7b0b2 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -53,7 +53,7 @@ struct Dialogue void load(ESMReader &esm); void save(ESMWriter &esm) const; - /// Remove all INFOs marked as QS_Deleted from mInfos. + /// Remove all INFOs that are deleted or marked as QS_Deleted from mInfos. void clearDeletedInfos(); /// Read the next info record From 377d606fc39cace8c291de001450025725479710 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 12 Jul 2015 15:20:22 +0300 Subject: [PATCH 280/441] Load/read methods in MWWorld::Store return a pair (record ID, deleted flag) (cherry picked from commit c266315a355480ad6e4bc665e5d4150c6c8de8f3) Conflicts: apps/openmw/mwworld/store.cpp apps/openmw/mwworld/store.hpp --- apps/openmw/mwworld/esmstore.cpp | 18 ++++++++---------- components/esm/util.hpp | 6 ------ 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 50a7e3376..2a302e082 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -95,22 +95,21 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) throw std::runtime_error(error.str()); } } else { - it->second->load(esm); - std::string id = it->second->getLastAddedRecordId(); - if (it->second->isLastAddedRecordDeleted()) + RecordId id = it->second->load(esm); + if (id.mIsDeleted) { - it->second->eraseStatic(id); + it->second->eraseStatic(id.mId); continue; } if (n.val==ESM::REC_DIAL) { - dialogue = const_cast(mDialogs.find(id)); + dialogue = const_cast(mDialogs.find(id.mId)); } else { dialogue = 0; } // Insert the reference into the global lookup - if (!id.empty() && isCacheableRecord(n.val)) { - mIds[Misc::StringUtils::lowerCase (id)] = n.val; + if (!id.mId.empty() && isCacheableRecord(n.val)) { + mIds[Misc::StringUtils::lowerCase (id.mId)] = n.val; } } listener->setProgress(static_cast(esm.getFileOffset() / (float)esm.getFileSize() * 1000)); @@ -183,13 +182,12 @@ void ESMStore::setUp() case ESM::REC_LEVC: { - StoreBase *store = mStores[type]; - store->read (reader); + RecordId id = mStores[type]->read (reader); // FIXME: there might be stale dynamic IDs in mIds from an earlier savegame // that really should be cleared instead of just overwritten - mIds[store->getLastAddedRecordId()] = type; + mIds[id.mId] = type; } if (type==ESM::REC_NPC_) diff --git a/components/esm/util.hpp b/components/esm/util.hpp index 258372e31..7e350808d 100644 --- a/components/esm/util.hpp +++ b/components/esm/util.hpp @@ -58,12 +58,6 @@ struct Vector3 bool readDeleSubRecord(ESMReader &esm); void writeDeleSubRecord(ESMWriter &esm); -template -std::string getRecordId(const RecordT &record) -{ - return record.mId; -} - template bool isRecordDeleted(const RecordT &record) { From 639e6e1c560fa7b74c264681f4f6500f06c926e2 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 12 Jul 2015 15:22:51 +0300 Subject: [PATCH 281/441] Remove DELE and NAME handling from RefIdCollection and RefIdData (cherry picked from commit 42f9136141657cbe3fd0801c579e37dd1ff47a30) --- apps/opencs/model/world/refidcollection.cpp | 56 +----------- apps/opencs/model/world/refiddata.cpp | 12 +-- apps/opencs/model/world/refiddata.hpp | 96 +++++++++++++++++---- 3 files changed, 87 insertions(+), 77 deletions(-) diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index a5e813338..9560539b2 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -848,61 +848,7 @@ const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (int index) con void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, UniversalId::Type type) { - std::string id = reader.getHNOString ("NAME"); - - int index = searchId (id); - - if (reader.isNextSub ("DELE")) - { - reader.skipRecord(); - - if (index==-1) - { - // deleting a record that does not exist - - // ignore it for now - - /// \todo report the problem to the user - } - else if (base) - { - mData.erase (index, 1); - } - else - { - mData.getRecord (mData.globalToLocalIndex (index)).mState = RecordBase::State_Deleted; - } - } - else - { - if (index==-1) - { - // new record - int index = mData.getAppendIndex (type); - mData.appendRecord (type, id, base); - - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); - - mData.load (localIndex, reader, base); - - mData.getRecord (localIndex).mState = - base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - } - else - { - // old record - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); - - if (!base) - if (mData.getRecord (localIndex).mState==RecordBase::State_Erased) - throw std::logic_error ("attempt to access a deleted record"); - - mData.load (localIndex, reader, base); - - if (!base) - mData.getRecord (localIndex).mState = RecordBase::State_Modified; - } - } + mData.load(reader, base, type); } int CSMWorld::RefIdCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 7696c3763..3d8d6aaef 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -161,15 +161,15 @@ int CSMWorld::RefIdData::getAppendIndex (UniversalId::Type type) const return index; } -void CSMWorld::RefIdData::load (const LocalIndex& index, ESM::ESMReader& reader, bool base) +void CSMWorld::RefIdData::load (ESM::ESMReader& reader, bool base, CSMWorld::UniversalId::Type type) { - std::map::iterator iter = - mRecordContainers.find (index.second); + std::map::iterator found = + mRecordContainers.find (type); - if (iter==mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + if (found == mRecordContainers.end()) + throw std::logic_error ("Invalid type for an Object (Reference ID)"); - iter->second->load (index.first, reader, base); + found->second->load(reader, base); } void CSMWorld::RefIdData::erase (const LocalIndex& index, int count) diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 8909ae4fb..e146dbee3 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -49,7 +49,7 @@ namespace CSMWorld virtual void insertRecord (RecordBase& record) = 0; - virtual void load (int index, ESM::ESMReader& reader, bool base) = 0; + virtual void load (ESM::ESMReader& reader, bool base) = 0; virtual void erase (int index, int count) = 0; @@ -73,7 +73,7 @@ namespace CSMWorld virtual void insertRecord (RecordBase& record); - virtual void load (int index, ESM::ESMReader& reader, bool base); + virtual void load (ESM::ESMReader& reader, bool base); virtual void erase (int index, int count); @@ -122,9 +122,67 @@ namespace CSMWorld } template - void RefIdDataContainer::load (int index, ESM::ESMReader& reader, bool base) + void RefIdDataContainer::load (ESM::ESMReader& reader, bool base) { - (base ? mContainer.at (index).mBase : mContainer.at (index).mModified).load (reader); + RecordT record; + record.load(reader); + + typename std::vector >::iterator found = mContainer.begin(); + for (; found != mContainer.end(); ++found) + { + if (found->get().mId == record.mId) + { + break; + } + } + + if (record.mIsDeleted) + { + if (found == mContainer.end()) + { + // deleting a record that does not exist + // ignore it for now + /// \todo report the problem to the user + return; + } + + if (base) + { + mContainer.erase(found); + } + else + { + found->mState = RecordBase::State_Deleted; + } + } + else + { + if (found == mContainer.end()) + { + appendRecord(record.mId, base); + if (base) + { + mContainer.back().mBase = record; + } + else + { + mContainer.back().mModified = record; + } + } + else + { + if (!base) + { + if (found->mState == RecordBase::State_Erased) + { + throw std::logic_error("Attempt to access a deleted record"); + } + + found->mState = RecordBase::State_Modified; + found->mModified = record; + } + } + } } template @@ -145,20 +203,26 @@ namespace CSMWorld template void RefIdDataContainer::save (int index, ESM::ESMWriter& writer) const { - CSMWorld::RecordBase::State state = mContainer.at (index).mState; + Record record = mContainer.at(index); + RecordT esmRecord; - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + switch (record.mState) { - writer.startRecord (mContainer.at (index).mModified.sRecordId); - writer.writeHNCString ("NAME", getId (index)); - mContainer.at (index).mModified.save (writer); - writer.endRecord (mContainer.at (index).mModified.sRecordId); - } - else if (state==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + case RecordBase::State_Modified: + case RecordBase::State_ModifiedOnly: + esmRecord = record.mModified; + break; + case RecordBase::State_Deleted: + esmRecord = record.mBase; + esmRecord.mIsDeleted = true; + break; + default: + break; } + + writer.startRecord(esmRecord.sRecordId); + esmRecord.save(writer); + writer.endRecord(esmRecord.sRecordId); } @@ -221,7 +285,7 @@ namespace CSMWorld int getAppendIndex (UniversalId::Type type) const; - void load (const LocalIndex& index, ESM::ESMReader& reader, bool base); + void load (ESM::ESMReader& reader, bool base, UniversalId::Type type); int getSize() const; From d518d7021264cf639ed01c97bd11d897f5afb845 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Mon, 13 Jul 2015 10:40:11 +0300 Subject: [PATCH 282/441] Remove NAME and DELE handling from IdCollection (cherry picked from commit 74a055f3ccbe25e50d2947d6fe639a84e3138ec7) --- apps/opencs/model/world/cell.cpp | 5 +- apps/opencs/model/world/data.cpp | 32 +++++------ apps/opencs/model/world/idcollection.hpp | 69 ++++++++---------------- apps/opencs/model/world/land.hpp | 10 ++++ apps/opencs/model/world/pathgrid.hpp | 10 ++++ components/esm/util.cpp | 36 +++++++++++++ components/esm/util.hpp | 24 +++++++++ 7 files changed, 119 insertions(+), 67 deletions(-) diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index 91becdb74..b8c78eb09 100644 --- a/apps/opencs/model/world/cell.cpp +++ b/apps/opencs/model/world/cell.cpp @@ -4,16 +4,13 @@ void CSMWorld::Cell::load (ESM::ESMReader &esm) { - mName = mId; - ESM::Cell::load (esm, false); + mId = mName; if (!(mData.mFlags & Interior)) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index dfb2d99d3..ce1c7135f 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1108,41 +1108,41 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) case ESM::REC_DIAL: { - std::string id = mReader->getHNOString ("NAME"); - ESM::Dialogue record; - record.mId = id; record.load (*mReader); - if (record.mType==ESM::Dialogue::Journal) - { - mJournals.load (record, mBase); - mDialogue = &mJournals.getRecord (id).get(); - } - else if (record.mType==ESM::Dialogue::Deleted) + if (record.mIsDeleted) { - mDialogue = 0; // record vector can be shuffled around which would make pointer - // to record invalid + // record vector can be shuffled around which would make pointer to record invalid + mDialogue = 0; - if (mJournals.tryDelete (id)) + if (mJournals.tryDelete (record.mId)) { /// \todo handle info records } - else if (mTopics.tryDelete (id)) + else if (mTopics.tryDelete (record.mId)) { /// \todo handle info records } else { messages.add (UniversalId::Type_None, - "Trying to delete dialogue record " + id + " which does not exist", + "Trying to delete dialogue record " + record.mId + " which does not exist", "", CSMDoc::Message::Severity_Warning); } } else { - mTopics.load (record, mBase); - mDialogue = &mTopics.getRecord (id).get(); + if (record.mType == ESM::Dialogue::Journal) + { + mJournals.load (record, mBase); + mDialogue = &mJournals.getRecord (record.mId).get(); + } + else + { + mTopics.load (record, mBase); + mDialogue = &mTopics.getRecord (record.mId).get(); + } } break; diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 4eafc59bd..41ce59702 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -2,6 +2,7 @@ #define CSM_WOLRD_IDCOLLECTION_H #include +#include #include "collection.hpp" @@ -41,69 +42,43 @@ namespace CSMWorld template int IdCollection::load (ESM::ESMReader& reader, bool base) { - std::string id = reader.getHNOString ("NAME"); + ESXRecordT record; + loadRecord (record, reader); - if (reader.isNextSub ("DELE")) - { - int index = Collection::searchId (id); - - reader.skipRecord(); + std::string id = IdAccessorT().getId (record); + int index = searchId (id); + if (ESM::isRecordDeleted (record)) + { if (index==-1) { // deleting a record that does not exist - // ignore it for now - /// \todo report the problem to the user + return -1; } - else if (base) + + if (base) { - Collection::removeRows (index, 1); + removeRows (index, 1); } else { - Record record = Collection::getRecord (index); - record.mState = RecordBase::State_Deleted; - this->setRecord (index, record); + Record baseRecord = getRecord (index); + baseRecord.mState = RecordBase::State_Deleted; + this->setRecord (index, baseRecord); } return -1; } - else - { - ESXRecordT record; - - // Sometimes id (i.e. NAME of the cell) may be different to the id we stored - // earlier. e.g. NAME == "Vivec, Arena" but id == "#-4 11". Sometime NAME is - // missing altogether for scripts or cells. - // - // In such cases the returned index will be -1. We then try updating the - // IdAccessor's id manually (e.g. set mId of the record to "Vivec, Arena") - // and try getting the index once more after loading the record. The mId of the - // record would have changed to "#-4 11" after the load, and searchId() should find - // it (if this is a modify) - int index = this->searchId (id); - - if (index==-1) - IdAccessorT().getId (record) = id; - else - { - record = this->getRecord (index).get(); - } - - loadRecord (record, reader); - - if (index==-1) - { - std::string newId = IdAccessorT().getId(record); - int newIndex = this->searchId(newId); - if (newIndex != -1 && id != newId) - index = newIndex; - } - - return load (record, base, index); - } + // + //if (index != -1) + //{ + // ESXRecordT existedRecord = getRecord(index).get(); + // IdAccessorT().getId(record) = IdAccessorT().getId(existedRecord); + //} + + return load (record, base, index); } template diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index 22cedb56d..1b79913be 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace CSMWorld { @@ -20,4 +21,13 @@ namespace CSMWorld }; } +namespace ESM +{ + template <> + bool isRecordDeleted(const CSMWorld::Land &land) + { + return false; + } +} + #endif diff --git a/apps/opencs/model/world/pathgrid.hpp b/apps/opencs/model/world/pathgrid.hpp index 7e7b7c3bb..cd5e472c8 100644 --- a/apps/opencs/model/world/pathgrid.hpp +++ b/apps/opencs/model/world/pathgrid.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace CSMWorld { @@ -26,4 +27,13 @@ namespace CSMWorld }; } +namespace ESM +{ + template <> + bool isRecordDeleted(const CSMWorld::Pathgrid &pgrd) + { + return false; + } +} + #endif diff --git a/components/esm/util.cpp b/components/esm/util.cpp index 4cfe644e8..a5ec377a3 100644 --- a/components/esm/util.cpp +++ b/components/esm/util.cpp @@ -36,4 +36,40 @@ namespace ESM { return false; } + + template <> + bool isRecordDeleted(const Skill &skill) + { + return false; + } + + template <> + bool isRecordDeleted(const MagicEffect &mgef) + { + return false; + } + + template <> + bool isRecordDeleted(const Pathgrid &pgrd) + { + return false; + } + + template <> + bool isRecordDeleted(const Land &land) + { + return false; + } + + template <> + bool isRecordDeleted(const DebugProfile &profile) + { + return false; + } + + template <> + bool isRecordDeleted(const Filter &filter) + { + return false; + } } diff --git a/components/esm/util.hpp b/components/esm/util.hpp index 7e350808d..233232cdd 100644 --- a/components/esm/util.hpp +++ b/components/esm/util.hpp @@ -12,6 +12,12 @@ #include "loadglob.hpp" #include "loadrace.hpp" #include "loadgmst.hpp" +#include "loadskil.hpp" +#include "loadmgef.hpp" +#include "loadland.hpp" +#include "loadpgrd.hpp" +#include "debugprofile.hpp" +#include "filter.hpp" namespace ESM { @@ -74,6 +80,24 @@ bool isRecordDeleted(const Race &race); template <> bool isRecordDeleted(const GameSetting &gmst); +template <> +bool isRecordDeleted(const Skill &skill); + +template <> +bool isRecordDeleted(const MagicEffect &mgef); + +template <> +bool isRecordDeleted(const Pathgrid &pgrd); + +template <> +bool isRecordDeleted(const Land &land); + +template <> +bool isRecordDeleted(const DebugProfile &profile); + +template <> +bool isRecordDeleted(const Filter &filter); + } #endif From 72152d84edddcf7d9d9824fe6a9c64931d324165 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Mon, 13 Jul 2015 10:53:31 +0300 Subject: [PATCH 283/441] Move ID loading into a separate method for Dialogue and DialInfo records (cherry picked from commit c8c79dc1efa5682c52ead7221628812638a55fed) Conflicts: apps/openmw/mwworld/store.cpp --- components/esm/loaddial.cpp | 39 +++++++++++++++++++++---------------- components/esm/loaddial.hpp | 6 ++++++ components/esm/loadinfo.cpp | 14 ++++++++++++- components/esm/loadinfo.hpp | 6 ++++++ 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index dfac0ce63..fcdb57c8d 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -19,9 +19,18 @@ namespace ESM void Dialogue::load(ESMReader &esm) { - mIsDeleted = false; + loadId(esm); + loadData(esm); + } + void Dialogue::loadId(ESMReader &esm) + { + mIsDeleted = false; mId = esm.getHNString("NAME"); + } + + void Dialogue::loadData(ESMReader &esm) + { esm.getSubNameIs("DATA"); esm.getSubHeader(); int si = esm.getSubSize(); @@ -60,31 +69,28 @@ namespace ESM void Dialogue::readInfo(ESMReader &esm, bool merge) { - const std::string& id = esm.getHNOString("INAM"); + ESM::DialInfo info; + info.loadId(esm); if (!merge || mInfo.empty()) { - ESM::DialInfo info; - info.mId = id; - info.load(esm); - mLookup[id] = mInfo.insert(mInfo.end(), info); + info.loadInfo(esm); + mLookup[info.mId] = mInfo.insert(mInfo.end(), info); return; } ESM::Dialogue::InfoContainer::iterator it = mInfo.end(); std::map::iterator lookup; + lookup = mLookup.find(info.mId); - lookup = mLookup.find(id); - - ESM::DialInfo info; if (lookup != mLookup.end()) { it = lookup->second; // Merge with existing record. Only the subrecords that are present in // the new record will be overwritten. - it->load(esm); + it->loadInfo(esm); info = *it; // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record @@ -93,18 +99,17 @@ namespace ESM } else { - info.mId = id; - info.load(esm); + info.loadInfo(esm); } if (info.mNext.empty()) { - mLookup[id] = mInfo.insert(mInfo.end(), info); + mLookup[info.mId] = mInfo.insert(mInfo.end(), info); return; } if (info.mPrev.empty()) { - mLookup[id] = mInfo.insert(mInfo.begin(), info); + mLookup[info.mId] = mInfo.insert(mInfo.begin(), info); return; } @@ -113,7 +118,7 @@ namespace ESM { it = lookup->second; - mLookup[id] = mInfo.insert(++it, info); + mLookup[info.mId] = mInfo.insert(++it, info); return; } @@ -122,11 +127,11 @@ namespace ESM { it = lookup->second; - mLookup[id] = mInfo.insert(it, info); + mLookup[info.mId] = mInfo.insert(it, info); return; } - std::cerr << "Failed to insert info " << id << std::endl; + std::cerr << "Failed to insert info " << info.mId << std::endl; } void Dialogue::clearDeletedInfos() diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index e80a7b0b2..73bf16974 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -51,6 +51,12 @@ struct Dialogue Dialogue(); void load(ESMReader &esm); + ///< Loads all sub-records of Dialogue record + void loadId(ESMReader &esm); + ///< Loads NAME sub-record of Dialogue record + void loadData(ESMReader &esm); + ///< Loads all sub-records of Dialogue record, except NAME sub-record + void save(ESMWriter &esm) const; /// Remove all INFOs that are deleted or marked as QS_Deleted from mInfos. diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index c1b12e24c..8f5f0f28b 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -14,10 +14,21 @@ namespace ESM {} void DialInfo::load(ESMReader &esm) + { + loadId(esm); + loadInfo(esm); + } + + void DialInfo::loadId(ESMReader &esm) + { + mIsDeleted = false; + mId = esm.getHNString("INAM"); + } + + void DialInfo::loadInfo(ESMReader &esm) { mQuestStatus = QS_None; mFactionLess = false; - mIsDeleted = false; mPrev = esm.getHNString("PNAM"); mNext = esm.getHNString("NNAM"); @@ -141,6 +152,7 @@ namespace ESM void DialInfo::save(ESMWriter &esm) const { + esm.writeHNCString("INAM", mId); esm.writeHNCString("PNAM", mPrev); esm.writeHNCString("NNAM", mNext); diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index fbb7e36a5..c243cd50e 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -111,6 +111,12 @@ struct DialInfo DialInfo(); void load(ESMReader &esm); + ///< Loads all sub-records of Info record + void loadId(ESMReader &esm); + ///< Loads only Id of Info record (INAM sub-record) + void loadInfo(ESMReader &esm); + ///< Loads all sub-records of Info record, except INAM sub-record + void save(ESMWriter &esm) const; void blank(); From bd695feded1d688df6ad6dfbbc2a86b393f4d542 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Mon, 13 Jul 2015 11:19:14 +0300 Subject: [PATCH 284/441] Remove INAM handling from InfoCollection (cherry picked from commit 71e5fc7f0458f77e94879399a39e36393ed19409) --- apps/opencs/model/world/infocollection.cpp | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 60c613041..b61718637 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -106,21 +106,18 @@ bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector Date: Mon, 13 Jul 2015 22:37:14 +0300 Subject: [PATCH 285/441] Add NAME handling to DebugProfile and Filter records (cherry picked from commit b55a4999caf2150afa6bc26d3ccc303a684131a0) --- components/esm/debugprofile.cpp | 2 ++ components/esm/filter.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 9d605a6af..4f7a92ae6 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -8,6 +8,7 @@ unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; void ESM::DebugProfile::load (ESMReader& esm) { + mId = esm.getHNString ("NAME"); mDescription = esm.getHNString ("DESC"); mScriptText = esm.getHNString ("SCRP"); esm.getHNT (mFlags, "FLAG"); @@ -15,6 +16,7 @@ void ESM::DebugProfile::load (ESMReader& esm) void ESM::DebugProfile::save (ESMWriter& esm) const { + esm.writeHNCString ("NAME", mId); esm.writeHNCString ("DESC", mDescription); esm.writeHNCString ("SCRP", mScriptText); esm.writeHNT ("FLAG", mFlags); diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 5bc768f72..01f45030d 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -8,12 +8,14 @@ unsigned int ESM::Filter::sRecordId = REC_FILT; void ESM::Filter::load (ESMReader& esm) { + mId = esm.getHNString ("NAME"); mFilter = esm.getHNString ("FILT"); mDescription = esm.getHNString ("DESC"); } void ESM::Filter::save (ESMWriter& esm) const { + esm.writeHNCString ("NAME", mId); esm.writeHNCString ("FILT", mFilter); esm.writeHNCString ("DESC", mDescription); } From 0979d88b0ca8781fa4b8ed7b57a1d77747002840 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 14 Jul 2015 16:18:33 +0300 Subject: [PATCH 286/441] Rework RefIdData code. Update the index map when a new record is loaded (cherry picked from commit 5e623a2a1d06d5679f214cad95123a1d3bd88b34) --- apps/opencs/model/world/refiddata.cpp | 30 +++++++++-- apps/opencs/model/world/refiddata.hpp | 73 +++++++++++---------------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 3d8d6aaef..2d8c9ac10 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -3,10 +3,20 @@ #include #include -#include - CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {} + +std::string CSMWorld::RefIdData::getRecordId(const CSMWorld::RefIdData::LocalIndex &index) const +{ + std::map::const_iterator found = + mRecordContainers.find (index.second); + + if (found == mRecordContainers.end()) + throw std::logic_error ("invalid local index type"); + + return found->second->getId(index.first); +} + CSMWorld::RefIdData::RefIdData() { mRecordContainers.insert (std::make_pair (UniversalId::Type_Activator, &mActivators)); @@ -167,9 +177,21 @@ void CSMWorld::RefIdData::load (ESM::ESMReader& reader, bool base, CSMWorld::Uni mRecordContainers.find (type); if (found == mRecordContainers.end()) - throw std::logic_error ("Invalid type for an Object (Reference ID)"); + throw std::logic_error ("Invalid Referenceable ID type"); - found->second->load(reader, base); + int index = found->second->load(reader, base); + if (index != -1) + { + LocalIndex localIndex = LocalIndex(index, type); + if (base && getRecord(localIndex).mState == RecordBase::State_Deleted) + { + erase(localIndex, 1); + } + else + { + mIndex[Misc::StringUtils::lowerCase(getRecordId(localIndex))] = localIndex; + } + } } void CSMWorld::RefIdData::erase (const LocalIndex& index, int count) diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index e146dbee3..a2922958a 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -25,6 +25,8 @@ #include #include +#include + #include "record.hpp" #include "universalid.hpp" @@ -49,7 +51,8 @@ namespace CSMWorld virtual void insertRecord (RecordBase& record) = 0; - virtual void load (ESM::ESMReader& reader, bool base) = 0; + virtual int load (ESM::ESMReader& reader, bool base) = 0; + ///< \return index of a loaded record or -1 if no record was loaded virtual void erase (int index, int count) = 0; @@ -73,7 +76,8 @@ namespace CSMWorld virtual void insertRecord (RecordBase& record); - virtual void load (ESM::ESMReader& reader, bool base); + virtual int load (ESM::ESMReader& reader, bool base); + ///< \return index of a loaded record or -1 if no record was loaded virtual void erase (int index, int count); @@ -122,15 +126,16 @@ namespace CSMWorld } template - void RefIdDataContainer::load (ESM::ESMReader& reader, bool base) + int RefIdDataContainer::load (ESM::ESMReader& reader, bool base) { RecordT record; record.load(reader); - typename std::vector >::iterator found = mContainer.begin(); - for (; found != mContainer.end(); ++found) + int index = 0; + int numRecords = static_cast(mContainer.size()); + for (; index < numRecords; ++index) { - if (found->get().mId == record.mId) + if (Misc::StringUtils::ciEqual(mContainer[index].get().mId, record.mId)) { break; } @@ -138,26 +143,21 @@ namespace CSMWorld if (record.mIsDeleted) { - if (found == mContainer.end()) + if (index == numRecords) { // deleting a record that does not exist // ignore it for now /// \todo report the problem to the user - return; + return -1; } - if (base) - { - mContainer.erase(found); - } - else - { - found->mState = RecordBase::State_Deleted; - } + // Flag the record as Deleted even for a base content file. + // RefIdData is responsible for its erasure. + mContainer[index].mState = RecordBase::State_Deleted; } else { - if (found == mContainer.end()) + if (index == numRecords) { appendRecord(record.mId, base); if (base) @@ -169,20 +169,13 @@ namespace CSMWorld mContainer.back().mModified = record; } } - else + else if (!base) { - if (!base) - { - if (found->mState == RecordBase::State_Erased) - { - throw std::logic_error("Attempt to access a deleted record"); - } - - found->mState = RecordBase::State_Modified; - found->mModified = record; - } + mContainer[index].setModified(record); } } + + return index; } template @@ -204,25 +197,15 @@ namespace CSMWorld void RefIdDataContainer::save (int index, ESM::ESMWriter& writer) const { Record record = mContainer.at(index); - RecordT esmRecord; + RecordT esmRecord = record.get(); - switch (record.mState) + if (record.isModified() || record.mState == RecordBase::State_Deleted) { - case RecordBase::State_Modified: - case RecordBase::State_ModifiedOnly: - esmRecord = record.mModified; - break; - case RecordBase::State_Deleted: - esmRecord = record.mBase; - esmRecord.mIsDeleted = true; - break; - default: - break; + esmRecord.mIsDeleted = (record.mState == RecordBase::State_Deleted); + writer.startRecord(esmRecord.sRecordId); + esmRecord.save(writer); + writer.endRecord(esmRecord.sRecordId); } - - writer.startRecord(esmRecord.sRecordId); - esmRecord.save(writer); - writer.endRecord(esmRecord.sRecordId); } @@ -262,6 +245,8 @@ namespace CSMWorld void erase (const LocalIndex& index, int count); ///< Must not spill over into another type. + std::string getRecordId(const LocalIndex &index) const; + public: RefIdData(); From 95031041291f5f0379e66c814eddd395b035c2ed Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 14 Jul 2015 23:31:16 +0300 Subject: [PATCH 287/441] Return a correct index for a loaded record that was deleted (cherry picked from commit a1389b87bacd08f54c4c146c7a0b6d1ed51edc54) --- apps/opencs/model/world/idcollection.hpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 41ce59702..d08abce5b 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -61,22 +61,14 @@ namespace CSMWorld if (base) { removeRows (index, 1); - } - else - { - Record baseRecord = getRecord (index); - baseRecord.mState = RecordBase::State_Deleted; - this->setRecord (index, baseRecord); + return -1; } - return -1; + Record baseRecord = getRecord (index); + baseRecord.mState = RecordBase::State_Deleted; + setRecord (index, baseRecord); + return index; } - // - //if (index != -1) - //{ - // ESXRecordT existedRecord = getRecord(index).get(); - // IdAccessorT().getId(record) = IdAccessorT().getId(existedRecord); - //} return load (record, base, index); } From 3686c1e32d9d59f773cafb12b5a8239ded1d3a79 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Wed, 15 Jul 2015 19:39:01 +0300 Subject: [PATCH 288/441] Move DELE handling to CellRef record (cherry picked from commit e8a9567be30fb35e78e252bf52e95bcebe76a109) --- apps/openmw/mwworld/cellreflist.hpp | 2 +- apps/openmw/mwworld/cellstore.cpp | 58 ++++++++++++++--------------- apps/openmw/mwworld/cellstore.hpp | 2 +- components/esm/cellref.cpp | 21 +++++++++++ components/esm/cellref.hpp | 4 ++ components/esm/loadcell.cpp | 12 ++---- components/esm/loadcell.hpp | 3 +- 7 files changed, 59 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 2c5e01aaa..5b3ad6262 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -22,7 +22,7 @@ namespace MWWorld /// and the build will fail with an ugly three-way cyclic header dependence /// so we need to pass the instantiation of the method to the linker, when /// all methods are known. - void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); + void load (ESM::CellRef &ref, const MWWorld::ESMStore &esmStore); LiveRef *find (const std::string& name) { diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 7da7c187d..03178001c 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -146,7 +146,7 @@ namespace MWWorld { template - void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) + void CellRefList::load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore) { const MWWorld::Store &store = esmStore.get(); @@ -157,7 +157,7 @@ namespace MWWorld LiveRef liveCellRef (ref, ptr); - if (deleted) + if (ref.mIsDeleted) liveCellRef.mData.setDeleted(true); if (iter != mList.end()) @@ -444,10 +444,9 @@ namespace MWWorld ESM::CellRef ref; // Get each reference in turn - bool deleted = false; - while (mCell->getNextRef (esm[index], ref, deleted)) + while (mCell->getNextRef (esm[index], ref)) { - if (deleted) + if (ref.mIsDeleted) continue; // Don't list reference if it was moved to a different cell. @@ -490,8 +489,7 @@ namespace MWWorld ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; // Get each reference in turn - bool deleted = false; - while(mCell->getNextRef(esm[index], ref, deleted)) + while(mCell->getNextRef(esm[index], ref)) { // Don't load reference if it was moved to a different cell. ESM::MovedCellRefTracker::const_iterator iter = @@ -500,7 +498,7 @@ namespace MWWorld continue; } - loadRef (ref, deleted, store); + loadRef (ref, store); } } @@ -509,7 +507,7 @@ namespace MWWorld { ESM::CellRef &ref = const_cast(*it); - loadRef (ref, false, store); + loadRef (ref, store); } } @@ -538,32 +536,32 @@ namespace MWWorld return Ptr(); } - void CellStore::loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store) + void CellStore::loadRef (ESM::CellRef& ref, const ESMStore& store) { Misc::StringUtils::toLower (ref.mRefID); switch (store.find (ref.mRefID)) { - case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; - case ESM::REC_ALCH: mPotions.load(ref, deleted, store); break; - case ESM::REC_APPA: mAppas.load(ref, deleted, store); break; - case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break; - case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break; - case ESM::REC_CLOT: mClothes.load(ref, deleted, store); break; - case ESM::REC_CONT: mContainers.load(ref, deleted, store); break; - case ESM::REC_CREA: mCreatures.load(ref, deleted, store); break; - case ESM::REC_DOOR: mDoors.load(ref, deleted, store); break; - case ESM::REC_INGR: mIngreds.load(ref, deleted, store); break; - case ESM::REC_LEVC: mCreatureLists.load(ref, deleted, store); break; - case ESM::REC_LEVI: mItemLists.load(ref, deleted, store); break; - case ESM::REC_LIGH: mLights.load(ref, deleted, store); break; - case ESM::REC_LOCK: mLockpicks.load(ref, deleted, store); break; - case ESM::REC_MISC: mMiscItems.load(ref, deleted, store); break; - case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; - case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; - case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; - case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; - case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; + case ESM::REC_ACTI: mActivators.load(ref, store); break; + case ESM::REC_ALCH: mPotions.load(ref,store); break; + case ESM::REC_APPA: mAppas.load(ref, store); break; + case ESM::REC_ARMO: mArmors.load(ref, store); break; + case ESM::REC_BOOK: mBooks.load(ref, store); break; + case ESM::REC_CLOT: mClothes.load(ref, store); break; + case ESM::REC_CONT: mContainers.load(ref, store); break; + case ESM::REC_CREA: mCreatures.load(ref, store); break; + case ESM::REC_DOOR: mDoors.load(ref, store); break; + case ESM::REC_INGR: mIngreds.load(ref, store); break; + case ESM::REC_LEVC: mCreatureLists.load(ref, store); break; + case ESM::REC_LEVI: mItemLists.load(ref, store); break; + case ESM::REC_LIGH: mLights.load(ref, store); break; + case ESM::REC_LOCK: mLockpicks.load(ref, store); break; + case ESM::REC_MISC: mMiscItems.load(ref, store); break; + case ESM::REC_NPC_: mNpcs.load(ref, store); break; + case ESM::REC_PROB: mProbes.load(ref, store); break; + case ESM::REC_REPA: mRepairs.load(ref, store); break; + case ESM::REC_STAT: mStatics.load(ref, store); break; + case ESM::REC_WEAP: mWeapons.load(ref, store); break; case 0: std::cerr << "Cell reference " + ref.mRefID + " not found!\n"; break; diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 672b6046b..6c4ba06f4 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -217,7 +217,7 @@ namespace MWWorld void loadRefs(const MWWorld::ESMStore &store, std::vector &esm); - void loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store); + void loadRef (ESM::CellRef& ref, const ESMStore& store); ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 33ac4a91e..713ae2f0b 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -2,6 +2,17 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "util.hpp" + +ESM::CellRef::CellRef() + : mScale(1.0f), + mFactionRank(-2), + mEnchantmentCharge(-1), + mGoldValue(1), + mChargeInt(-1), + mLockLevel(0), + mIsDeleted(false) +{} void ESM::RefNum::load (ESMReader& esm, bool wide) { @@ -26,6 +37,7 @@ void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) { + mIsDeleted = false; loadId(esm, wideRefNum); loadData(esm); } @@ -96,6 +108,8 @@ void ESM::CellRef::loadData(ESMReader &esm) if (esm.isNextSub("NAM0")) esm.skipHSub(); + + mIsDeleted = readDeleSubRecord (esm); } void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const @@ -148,6 +162,11 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons if (!inInventory) esm.writeHNT("DATA", mPos, 24); + + if (mIsDeleted) + { + writeDeleSubRecord(esm); + } } void ESM::CellRef::blank() @@ -177,6 +196,8 @@ void ESM::CellRef::blank() mPos.pos[i] = 0; mPos.rot[i] = 0; } + + mIsDeleted = false; } bool ESM::operator== (const RefNum& left, const RefNum& right) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index e9959611b..553dbaae3 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -99,6 +99,10 @@ namespace ESM // Position and rotation of this object within the cell Position mPos; + bool mIsDeleted; + + CellRef(); + /// Calls loadId and loadData void load (ESMReader& esm, bool wideRefNum = false); diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 86b4e4edb..67701a5b7 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -177,7 +177,7 @@ std::string Cell::getDescription() const } } -bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted, bool ignoreMoves, MovedCellRef *mref) +bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool ignoreMoves, MovedCellRef *mref) { // TODO: Try and document reference numbering, I don't think this has been done anywhere else. if (!esm.hasMoreSubs()) @@ -206,14 +206,6 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted, bool ignoreMo // Identify references belonging to a parent file and adapt the ID accordingly. adjustRefNum (ref.mRefNum, esm); - if (esm.isNextSub("DELE")) - { - esm.skipHSub(); - deleted = true; - } - else - deleted = false; - return true; } @@ -244,6 +236,8 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) mAmbi.mSunlight = 0; mAmbi.mFog = 0; mAmbi.mFogDensity = 0; + + mIsDeleted = false; } CellId Cell::getCellId() const diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index bf65c5fa8..a1a758e3b 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -163,8 +163,7 @@ struct Cell reuse one memory location without blanking it between calls. */ /// \param ignoreMoves ignore MVRF record and read reference like a regular CellRef. - static bool getNextRef(ESMReader &esm, - CellRef &ref, bool& deleted, bool ignoreMoves = false, MovedCellRef *mref = 0); + static bool getNextRef(ESMReader &esm, CellRef &ref, bool ignoreMoves = false, MovedCellRef *mref = 0); /* This fetches an MVRF record, which is used to track moved references. * Since they are comparably rare, we use a separate method for this. From 4e199697cce60f8b2e853982b6564ac46c94a46a Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Wed, 15 Jul 2015 20:53:08 +0300 Subject: [PATCH 289/441] Handle deleted records in RefCollection (cherry picked from commit 3ba73f5fd9feb9727b56f32767c848e520d9a94c) --- apps/opencs/model/world/refcollection.cpp | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index f8818807b..0b25f2711 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -19,12 +19,10 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Cell& cell2 = base ? cell.mBase : cell.mModified; CellRef ref; - - bool deleted = false; ESM::MovedCellRef mref; // hack to initialise mindex - while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, deleted, true, &mref)) + while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, true, &mref)) { // Keep mOriginalCell empty when in modified (as an indicator that the // original cell will always be equal the current cell). @@ -49,17 +47,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30 ref.mOriginalCell = cell2.mId; - if (deleted) - { - // FIXME: how to mark the record deleted? - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, - mCells.getId (cellIndex)); - - messages.add (id, "Moved reference "+ref.mRefID+" is in DELE state"); - - continue; - } - // It is not always possibe to ignore moved references sub-record and // calculate from coordinates. Some mods may place the ref in positions // outside normal bounds, resulting in non sensical cell id's. This often @@ -91,7 +78,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool break; } - if (deleted) + if (ref.mIsDeleted) { if (iter==cache.end()) { @@ -99,7 +86,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool mCells.getId (cellIndex)); messages.add (id, "Attempt to delete a non-existing reference"); - continue; } @@ -107,7 +93,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Record record = getRecord (index); - if (record.mState==RecordBase::State_BaseOnly) + if (base) { removeRows (index, 1); cache.erase (iter); From 8b7b3d2a4e1b670fe00bb8aa1393cbf6134c300e Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 16 Jul 2015 19:52:31 +0300 Subject: [PATCH 290/441] Refine DELE handling in ESM records. Add position-independent DELE search (cherry picked from commit ad353e6dd0b41e388e2ec3fbcc4bf15d1ef71e57) --- components/esm/cellref.cpp | 126 +++++++++++++--------- components/esm/esmreader.cpp | 5 + components/esm/esmreader.hpp | 3 + components/esm/loadacti.cpp | 23 ++-- components/esm/loadalch.cpp | 28 +++-- components/esm/loadappa.cpp | 27 +++-- components/esm/loadarmo.cpp | 26 +++-- components/esm/loadbody.cpp | 24 +++-- components/esm/loadbook.cpp | 25 +++-- components/esm/loadbsgn.cpp | 20 +++- components/esm/loadclas.cpp | 25 +++-- components/esm/loadclot.cpp | 26 +++-- components/esm/loadcont.cpp | 32 ++++-- components/esm/loadcrea.cpp | 33 ++++-- components/esm/loaddial.cpp | 46 +++++--- components/esm/loaddoor.cpp | 23 ++-- components/esm/loadench.cpp | 25 +++-- components/esm/loadfact.cpp | 28 +++-- components/esm/loadglob.cpp | 29 +++-- components/esm/loadinfo.cpp | 189 +++++++++++++-------------------- components/esm/loadinfo.hpp | 3 +- components/esm/loadingr.cpp | 24 +++-- components/esm/loadlevlist.cpp | 85 +++++++++------ components/esm/loadligh.cpp | 25 +++-- components/esm/loadlock.cpp | 26 +++-- components/esm/loadltex.cpp | 43 ++++++-- components/esm/loadmisc.cpp | 27 +++-- components/esm/loadnpc.cpp | 34 +++--- components/esm/loadprob.cpp | 27 +++-- components/esm/loadregn.cpp | 101 ++++++++++++------ components/esm/loadrepa.cpp | 27 +++-- components/esm/loadscpt.cpp | 29 +++-- components/esm/loadsndg.cpp | 38 ++++--- components/esm/loadsoun.cpp | 27 +++-- components/esm/loadspel.cpp | 30 ++++-- components/esm/loadstat.cpp | 39 +++++-- components/esm/loadweap.cpp | 24 +++-- 37 files changed, 857 insertions(+), 515 deletions(-) diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 713ae2f0b..c4fc93ff5 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -2,7 +2,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "util.hpp" ESM::CellRef::CellRef() : mScale(1.0f), @@ -37,7 +36,6 @@ void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) { - mIsDeleted = false; loadId(esm, wideRefNum); loadData(esm); } @@ -54,62 +52,90 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) mRefNum.load (esm, wideRefNum); mRefID = esm.getHNString ("NAME"); + mIsDeleted = false; } void ESM::CellRef::loadData(ESMReader &esm) { - // Again, UNAM sometimes appears after NAME and sometimes later. - // Or perhaps this UNAM means something different? - mReferenceBlocked = -1; - esm.getHNOT (mReferenceBlocked, "UNAM"); - - mScale = 1.0; - esm.getHNOT (mScale, "XSCL"); - - mOwner = esm.getHNOString ("ANAM"); - mGlobalVariable = esm.getHNOString ("BNAM"); - mSoul = esm.getHNOString ("XSOL"); - - mFaction = esm.getHNOString ("CNAM"); + mScale = 1.0f; mFactionRank = -2; - esm.getHNOT (mFactionRank, "INDX"); - - mGoldValue = 1; mChargeInt = -1; mEnchantmentCharge = -1; + mGoldValue = 1; + mLockLevel = 0; + mReferenceBlocked = -1; + mTeleport = false; + mIsDeleted = false; - esm.getHNOT (mEnchantmentCharge, "XCHG"); - - esm.getHNOT (mChargeInt, "INTV"); - - esm.getHNOT (mGoldValue, "NAM9"); - - // Present for doors that teleport you to another cell. - if (esm.isNextSub ("DODT")) + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) { - mTeleport = true; - esm.getHT (mDoorDest); - mDestCell = esm.getHNOString ("DNAM"); + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'U','N','A','M'>::value: + esm.getHT(mReferenceBlocked); + break; + case ESM::FourCC<'X','S','C','L'>::value: + esm.getHT(mScale); + break; + case ESM::FourCC<'A','N','A','M'>::value: + mOwner = esm.getHString(); + break; + case ESM::FourCC<'B','N','A','M'>::value: + mGlobalVariable = esm.getHString(); + break; + case ESM::FourCC<'X','S','O','L'>::value: + mSoul = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + mFaction = esm.getHString(); + break; + case ESM::FourCC<'I','N','D','X'>::value: + esm.getHT(mFactionRank); + break; + case ESM::FourCC<'X','C','H','G'>::value: + esm.getHT(mEnchantmentCharge); + break; + case ESM::FourCC<'I','N','T','V'>::value: + esm.getHT(mChargeInt); + break; + case ESM::FourCC<'N','A','M','9'>::value: + esm.getHT(mGoldValue); + break; + case ESM::FourCC<'D','O','D','T'>::value: + esm.getHT(mDoorDest); + mTeleport = true; + break; + case ESM::FourCC<'D','N','A','M'>::value: + mDestCell = esm.getHString(); + break; + case ESM::FourCC<'F','L','T','V'>::value: + esm.getHT(mLockLevel); + break; + case ESM::FourCC<'K','N','A','M'>::value: + mKey = esm.getHString(); + break; + case ESM::FourCC<'T','N','A','M'>::value: + mTrap = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mPos, 24); + break; + case ESM::FourCC<'N','A','M','0'>::value: + esm.skipHSub(); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } } - else - mTeleport = false; - - mLockLevel = 0; //Set to 0 to indicate no lock - esm.getHNOT (mLockLevel, "FLTV"); - - mKey = esm.getHNOString ("KNAM"); - mTrap = esm.getHNOString ("TNAM"); - - esm.getHNOT (mReferenceBlocked, "UNAM"); - if (esm.isNextSub("FLTV")) // no longer used - esm.skipHSub(); - - esm.getHNOT(mPos, "DATA", 24); - - if (esm.isNextSub("NAM0")) - esm.skipHSub(); - - mIsDeleted = readDeleSubRecord (esm); } void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const @@ -148,7 +174,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons } if (!inInventory && mLockLevel != 0) { - esm.writeHNT("FLTV", mLockLevel); + esm.writeHNT("FLTV", mLockLevel); } if (!inInventory) @@ -165,7 +191,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); } } diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 77ac0ae32..97fd8d986 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -185,6 +185,11 @@ bool ESMReader::peekNextSub(const char *name) return mCtx.subName == name; } +void ESMReader::cacheSubName() +{ + mCtx.subCached = true; +} + // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void ESMReader::getSubName() diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 4e92b7e5f..66ef98130 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -187,6 +187,9 @@ public: bool peekNextSub(const char* name); + // Store the current subrecord name for the next call of getSubName() + void cacheSubName(); + // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void getSubName(); diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index 14a3abe54..c32cea1a6 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,18 +14,23 @@ namespace ESM void Activator::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -38,15 +42,20 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } void Activator::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index 5faeb99e1..c1213583d 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -16,13 +15,9 @@ namespace ESM void Potion::load(ESMReader &esm) { mEffects.mList.clear(); + mIsDeleted = false; - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } - + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -30,6 +25,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -51,17 +54,22 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing ALDT"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) + esm.fail("Missing ALDT subrecord"); } void Potion::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index ea375aa7f..edf1f473b 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,12 +14,9 @@ namespace ESM void Apparatus::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -28,6 +24,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -46,18 +50,23 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing AADT"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) + esm.fail("Missing AADT subrecord"); } void Apparatus::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index d23a71cac..d5b9fdd44 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -46,13 +45,9 @@ namespace ESM void Armor::load(ESMReader &esm) { mParts.mParts.clear(); + mIsDeleted = false; - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } - + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -60,6 +55,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -84,18 +87,23 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing CTDT subrecord"); } void Armor::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index e0ebfd539..e2c6ad7b2 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,12 +14,9 @@ namespace ESM void BodyPart::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -28,6 +24,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -40,19 +44,23 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing BYDT subrecord"); } void BodyPart::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index 2824b6200..2d0d3ce75 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,12 +14,9 @@ namespace ESM void Book::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -28,6 +24,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -52,17 +56,22 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing BKDT subrecord"); } void Book::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 8cdeed3f6..9f5cd7270 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -16,16 +16,23 @@ namespace ESM void BirthSign::load(ESMReader &esm) { mPowers.mList.clear(); + mIsDeleted = false; - mIsDeleted = readDeleSubRecord(esm); - mId = esm.getHNString("NAME"); - + bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -40,16 +47,21 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } void BirthSign::save(ESMWriter &esm) const { if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); } + esm.writeHNCString("NAME", mId); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("TNAM", mTexture); diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index 1384a6280..b58c35d90 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -5,7 +5,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -45,12 +44,9 @@ namespace ESM void Class::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -58,6 +54,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -72,17 +76,22 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing CLDT subrecord"); } void Class::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 88f2e5715..18f7cd44f 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -16,13 +15,9 @@ namespace ESM void Clothing::load(ESMReader &esm) { mParts.mParts.clear(); + mIsDeleted = false; - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } - + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -30,6 +25,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -54,18 +57,23 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing CTDT subrecord"); } void Clothing::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 3d3d7fced..fadfe5f0f 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -26,19 +25,17 @@ namespace ESM unsigned int Container::sRecordId = REC_CONT; Container::Container() - : mIsDeleted(false) + : mWeight(0), + mFlags(0x8), + mIsDeleted(false) {} void Container::load(ESMReader &esm) { mInventory.mList.clear(); + mIsDeleted = false; - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } - + bool hasName = false; bool hasWeight = false; bool hasFlags = false; while (esm.hasMoreSubs()) @@ -47,6 +44,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -73,20 +78,25 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasWeight) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasWeight && !mIsDeleted) esm.fail("Missing CNDT subrecord"); - if (!hasFlags) + if (!hasFlags && !mIsDeleted) esm.fail("Missing FLAG subrecord"); } void Container::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 1aead6c04..b58e66239 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -3,14 +3,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { unsigned int Creature::sRecordId = REC_CREA; Creature::Creature() - : mIsDeleted(false) + : mFlags(0), + mScale(0.0f), + mIsDeleted(false) {} void Creature::load(ESMReader &esm) @@ -22,14 +23,11 @@ namespace ESM { mSpells.mList.clear(); mTransport.mList.clear(); - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } - mScale = 1.f; mHasAI = false; + mIsDeleted = false; + + bool hasName = false; bool hasNpdt = false; bool hasFlags = false; while (esm.hasMoreSubs()) @@ -38,6 +36,14 @@ namespace ESM { uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -85,20 +91,25 @@ namespace ESM { break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasNpdt) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasNpdt && !mIsDeleted) esm.fail("Missing NPDT subrecord"); - if (!hasFlags) + if (!hasFlags && !mIsDeleted) esm.fail("Missing FLAG subrecord"); } void Creature::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index fcdb57c8d..c517dc722 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -7,7 +7,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -31,20 +30,36 @@ namespace ESM void Dialogue::loadData(ESMReader &esm) { - esm.getSubNameIs("DATA"); - esm.getSubHeader(); - int si = esm.getSubSize(); - if (si == 1) - esm.getT(mType); - else if (si == 4) // The dialogue is deleted + while (esm.hasMoreSubs()) { - int32_t empty; - esm.getT(empty); // Skip an empty DATA - mIsDeleted = readDeleSubRecord(esm); - mType = Unknown; + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'D','A','T','A'>::value: + { + esm.getSubHeader(); + int size = esm.getSubSize(); + if (size == 1) + { + esm.getT(mType); + } + else + { + esm.skip(size); + } + break; + } + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mType = Unknown; + mIsDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } - else - esm.fail("Unknown sub record size"); } void Dialogue::save(ESMWriter &esm) const @@ -52,8 +67,7 @@ namespace ESM esm.writeHNCString("NAME", mId); if (mIsDeleted) { - esm.writeHNT("DATA", static_cast(0)); - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); } else { @@ -138,7 +152,7 @@ namespace ESM { for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); ) { - if (it->mIsDeleted || it->mQuestStatus == DialInfo::QS_Deleted) + if (it->mIsDeleted) it = mInfo.erase(it); else ++it; diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index 87382fa7b..4f58a4261 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,18 +14,23 @@ namespace ESM void Door::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -44,16 +48,21 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } void Door::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 1518e0385..0e480c379 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -16,13 +15,9 @@ namespace ESM void Enchantment::load(ESMReader &esm) { mEffects.mList.clear(); + mIsDeleted = false; - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } - + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -30,6 +25,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'E','N','D','T'>::value: esm.getHT(mData, 16); hasData = true; @@ -42,16 +45,20 @@ namespace ESM break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing ENDT subrecord"); } void Enchantment::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 53f3aa5a6..8538b0b95 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -5,7 +5,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -33,17 +32,13 @@ namespace ESM void Faction::load(ESMReader &esm) { + mIsDeleted = false; mReactions.clear(); for (int i=0;i<10;++i) mRanks[i].clear(); - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } - - int rankCounter=0; + int rankCounter = 0; + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -51,6 +46,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -75,18 +78,23 @@ namespace ESM } default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing FADT subrecord"); } void Faction::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index 392df02b5..5f96aff1f 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,30 +14,38 @@ namespace ESM void Global::load (ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) + mIsDeleted = false; + mId = esm.getHNString ("NAME"); + + if (esm.isNextSub ("DELE")) { - return; + esm.skipHSub(); + mIsDeleted = true; + } + else + { + mValue.read (esm, ESM::Variant::Format_Global); } - - mValue.read (esm, ESM::Variant::Format_Global); } void Global::save (ESMWriter &esm) const { - esm.writeHNCString("NAME", mId); + esm.writeHNCString ("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); - return; + esm.writeHNCString ("DELE", ""); + } + else + { + mValue.write (esm, ESM::Variant::Format_Global); } - - mValue.write (esm, ESM::Variant::Format_Global); } void Global::blank() { mValue.setType (ESM::VT_None); + mIsDeleted = false; } bool operator== (const Global& left, const Global& right) diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 8f5f0f28b..89fd4e0cd 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -3,14 +3,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { unsigned int DialInfo::sRecordId = REC_INFO; DialInfo::DialInfo() - : mIsDeleted(false) + : mFactionLess(false), + mQuestStatus(QS_None), + mIsDeleted(false) {} void DialInfo::load(ESMReader &esm) @@ -29,6 +30,7 @@ namespace ESM { mQuestStatus = QS_None; mFactionLess = false; + mIsDeleted = false; mPrev = esm.getHNString("PNAM"); mNext = esm.getHNString("NNAM"); @@ -36,118 +38,77 @@ namespace ESM // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings mSelects.clear(); - // If the info is deleted, NAME and DELE sub-records are followed after NNAM - if (esm.isNextSub("NAME")) - { - mResponse = esm.getHString(); - mIsDeleted = readDeleSubRecord(esm); - return; - } - - esm.getSubNameIs("DATA"); - esm.getHT(mData, 12); - - if (!esm.hasMoreSubs()) - return; - - // What follows is somewhat spaghetti-ish, but it's worth if for - // an extra speedup. INFO is by far the most common record type. - - // subName is a reference to the original, so it changes whenever - // a new sub name is read. esm.isEmptyOrGetName() will get the - // next name for us, or return true if there are no more records. - esm.getSubName(); - const NAME &subName = esm.retSubName(); - - if (subName.val == REC_ONAM) - { - mActor = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_RNAM) - { - mRace = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_CNAM) - { - mClass = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - - if (subName.val == REC_FNAM) - { - mFaction = esm.getHString(); - if (mFaction == "FFFF") - mFactionLess = true; - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_ANAM) - { - mCell = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_DNAM) - { - mPcFaction = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_SNAM) - { - mSound = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_NAME) - { - mResponse = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - - while (subName.val == REC_SCVR) + while (esm.hasMoreSubs()) { - SelectStruct ss; - - ss.mSelectRule = esm.getHString(); - - ss.mValue.read (esm, Variant::Format_Info); - - mSelects.push_back(ss); - - if (esm.isEmptyOrGetName()) - return; + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + break; + case ESM::FourCC<'O','N','A','M'>::value: + mActor = esm.getHString(); + break; + case ESM::FourCC<'R','N','A','M'>::value: + mRace = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + mClass = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + { + mFaction = esm.getHString(); + if (mFaction == "FFFF") + { + mFactionLess = true; + } + break; + } + case ESM::FourCC<'A','N','A','M'>::value: + mCell = esm.getHString(); + break; + case ESM::FourCC<'D','N','A','M'>::value: + mPcFaction = esm.getHString(); + break; + case ESM::FourCC<'S','N','A','M'>::value: + mSound = esm.getHString(); + break; + case ESM::FourCC<'N','A','M','E'>::value: + mResponse = esm.getHString(); + break; + case ESM::FourCC<'S','C','V','R'>::value: + { + SelectStruct ss; + ss.mSelectRule = esm.getHString(); + ss.mValue.read(esm, Variant::Format_Info); + mSelects.push_back(ss); + break; + } + case ESM::FourCC<'B','N','A','M'>::value: + mResultScript = esm.getHString(); + break; + case ESM::FourCC<'Q','S','T','N'>::value: + mQuestStatus = QS_Name; + esm.skipRecord(); + break; + case ESM::FourCC<'Q','S','T','F'>::value: + mQuestStatus = QS_Finished; + esm.skipRecord(); + break; + case ESM::FourCC<'Q','S','T','R'>::value: + mQuestStatus = QS_Restart; + esm.skipRecord(); + break; + default: + esm.fail("Unknown subrecord"); + break; + } } - - if (subName.val == REC_BNAM) - { - mResultScript = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - - if (subName.val == REC_QSTN) - mQuestStatus = QS_Name; - else if (subName.val == REC_QSTF) - mQuestStatus = QS_Finished; - else if (subName.val == REC_QSTR) - mQuestStatus = QS_Restart; - else if (subName.val == REC_DELE) - mQuestStatus = QS_Deleted; - else - esm.fail( - "Don't know what to do with " + subName.toString() - + " in INFO " + mId); - - if (mQuestStatus != QS_None) - // Skip rest of record - esm.skipRecord(); } void DialInfo::save(ESMWriter &esm) const @@ -158,8 +119,7 @@ namespace ESM if (mIsDeleted) { - esm.writeHNCString("NAME", mResponse); - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } @@ -186,7 +146,6 @@ namespace ESM case QS_Name: esm.writeHNT("QSTN",'\1'); break; case QS_Finished: esm.writeHNT("QSTF", '\1'); break; case QS_Restart: esm.writeHNT("QSTR", '\1'); break; - case QS_Deleted: esm.writeHNT("DELE", '\1'); break; default: break; } } diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index c243cd50e..65363d1be 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -59,8 +59,7 @@ struct DialInfo QS_None = 0, QS_Name = 1, QS_Finished = 2, - QS_Restart = 3, - QS_Deleted + QS_Restart = 3 }; // Rules for when to include this item in the final list of options diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index a7018b36d..51a1f4805 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,12 +14,9 @@ namespace ESM void Ingredient::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -28,6 +24,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -46,10 +50,13 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing IRDT subrecord"); // horrible hack to fix broken data in records @@ -79,9 +86,10 @@ namespace ESM void Ingredient::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index 1e07086bc..9c34ef657 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -13,49 +12,67 @@ namespace ESM void LevelledListBase::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } - - esm.getHNT(mFlags, "DATA"); - esm.getHNT(mChanceNone, "NNAM"); + mIsDeleted = false; - if (esm.isNextSub("INDX")) - { - int len; - esm.getHT(len); - mList.resize(len); - } - else + bool hasName = false; + while (esm.hasMoreSubs()) { - // Original engine ignores rest of the record, even if there are items following - mList.clear(); - esm.skipRecord(); - return; - } + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mFlags); + break; + case ESM::FourCC<'N','N','A','M'>::value: + esm.getHT(mChanceNone); + break; + case ESM::FourCC<'I','N','D','X'>::value: + { + int length = 0; + esm.getHT(length); + mList.resize(length); - // If this levelled list was already loaded by a previous content file, - // we overwrite the list. Merging lists should probably be left to external tools, - // with the limited amount of information there is in the records, all merging methods - // will be flawed in some way. For a proper fix the ESM format would have to be changed - // to actually track list changes instead of including the whole list for every file - // that does something with that list. - - for (size_t i = 0; i < mList.size(); i++) - { - LevelItem &li = mList[i]; - li.mId = esm.getHNString(mRecName); - esm.getHNT(li.mLevel, "INTV"); + // If this levelled list was already loaded by a previous content file, + // we overwrite the list. Merging lists should probably be left to external tools, + // with the limited amount of information there is in the records, all merging methods + // will be flawed in some way. For a proper fix the ESM format would have to be changed + // to actually track list changes instead of including the whole list for every file + // that does something with that list. + for (size_t i = 0; i < mList.size(); i++) + { + LevelItem &li = mList[i]; + li.mId = esm.getHNString(mRecName); + esm.getHNT(li.mLevel, "INTV"); + } + break; + } + default: + mList.clear(); + esm.skipRecord(); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } + void LevelledListBase::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index a153d500a..441e96d0a 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,12 +14,9 @@ namespace ESM void Light::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -28,6 +24,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -49,17 +53,22 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing LHDT subrecord"); } void Light::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 3b169af33..5ee041dab 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,19 +14,23 @@ namespace ESM void Lockpick::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; - bool hasData = true; + bool hasName = false; + bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -46,18 +49,23 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing LKDT subrecord"); } void Lockpick::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index 13315e684..7c14536ed 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,17 +14,49 @@ namespace ESM void LandTexture::load(ESMReader &esm) { - mIsDeleted = readDeleSubRecord(esm); - mId = esm.getHNString("NAME"); - esm.getHNT(mIndex, "INTV"); - mTexture = esm.getHNString("DATA"); + mIsDeleted = false; + + bool hasName = false; + bool hasIndex = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = false; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; + case ESM::FourCC<'I','N','T','V'>::value: + esm.getHT(mIndex); + hasIndex = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + mTexture = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasIndex) + esm.fail("Missing INTV subrecord"); } void LandTexture::save(ESMWriter &esm) const { if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); } + esm.writeHNCString("NAME", mId); esm.writeHNT("INTV", mIndex); esm.writeHNCString("DATA", mTexture); diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index 08cbcf741..de9ccdd6a 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,12 +14,9 @@ namespace ESM void Miscellaneous::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -28,6 +24,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -44,18 +48,25 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + default: + esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing MCDT subrecord"); } void Miscellaneous::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 1bdd5d483..ffbbb59a0 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -3,40 +3,45 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { unsigned int NPC::sRecordId = REC_NPC_; NPC::NPC() - : mIsDeleted(false) + : mFlags(0), + mHasAI(false), + mIsDeleted(false) {} void NPC::load(ESMReader &esm) { + mIsDeleted = false; mPersistent = (esm.getRecordFlags() & 0x0400) != 0; mSpells.mList.clear(); mInventory.mList.clear(); mTransport.mList.clear(); mAiPackage.mList.clear(); + mHasAI = false; - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } - + bool hasName = false; bool hasNpdt = false; bool hasFlags = false; - mHasAI = false; while (esm.hasMoreSubs()) { esm.getSubName(); uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -105,19 +110,24 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasNpdt) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasNpdt && !mIsDeleted) esm.fail("Missing NPDT subrecord"); - if (!hasFlags) + if (!hasFlags && !mIsDeleted) esm.fail("Missing FLAG subrecord"); } void NPC::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index f5287f986..4ce9b9d9c 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,19 +14,24 @@ namespace ESM void Probe::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; - bool hasData = true; + bool hasName = false; + bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -46,18 +50,23 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing PBDT subrecord"); } void Probe::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 2d99947b0..b48ffa4b7 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -3,64 +3,95 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { unsigned int Region::sRecordId = REC_REGN; Region::Region() - : mIsDeleted(false) + : mMapColor(0), + mIsDeleted(false) {} void Region::load(ESMReader &esm) { - mIsDeleted = readDeleSubRecord(esm); - mId = esm.getHNString("NAME"); - mName = esm.getHNOString("FNAM"); + mIsDeleted = false; - esm.getSubNameIs("WEAT"); - esm.getSubHeader(); - if (esm.getVer() == VER_12) - { - mData.mA = 0; - mData.mB = 0; - esm.getExact(&mData, sizeof(mData) - 2); - } - else if (esm.getVer() == VER_13) + bool hasName = false; + while (esm.hasMoreSubs()) { - // May include the additional two bytes (but not necessarily) - if (esm.getSubSize() == sizeof(mData)) - esm.getExact(&mData, sizeof(mData)); - else + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) { - mData.mA = 0; - mData.mB = 0; - esm.getExact(&mData, sizeof(mData)-2); + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'W','E','A','T'>::value: + { + esm.getSubHeader(); + if (esm.getVer() == VER_12) + { + mData.mA = 0; + mData.mB = 0; + esm.getExact(&mData, sizeof(mData) - 2); + } + else if (esm.getVer() == VER_13) + { + // May include the additional two bytes (but not necessarily) + if (esm.getSubSize() == sizeof(mData)) + { + esm.getExact(&mData, sizeof(mData)); + } + else + { + mData.mA = 0; + mData.mB = 0; + esm.getExact(&mData, sizeof(mData)-2); + } + } + else + { + esm.fail("Don't know what to do in this version"); + } + break; + } + case ESM::FourCC<'B','N','A','M'>::value: + mSleepList = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + esm.getHT(mMapColor); + break; + case ESM::FourCC<'S','N','A','M'>::value: + SoundRef sr; + esm.getHT(sr, 33); + mSoundList.push_back(sr); + break; + default: + esm.fail("Unknown subrecord"); + break; } } - else - esm.fail("Don't know what to do in this version"); - - mSleepList = esm.getHNOString("BNAM"); - esm.getHNT(mMapColor, "CNAM"); - - mSoundList.clear(); - while (esm.hasMoreSubs()) - { - SoundRef sr; - esm.getHNT(sr, "SNAM", 33); - mSoundList.push_back(sr); - } + if (!hasName) + esm.fail("Missing NAME subrecord"); } void Region::save(ESMWriter &esm) const { if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); } + esm.writeHNString("NAME", mId); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index fb213efd8..74e682d63 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,19 +14,24 @@ namespace ESM void Repair::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; - bool hasData = true; + bool hasName = false; + bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -46,18 +50,23 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing RIDT subrecord"); } void Repair::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index e6c516695..66d9d0057 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -62,23 +61,27 @@ namespace ESM void Script::load(ESMReader &esm) { - SCHD data; - esm.getHNT(data, "SCHD", 52); - mData = data.mData; - mId = data.mName.toString(); - - // In scripts DELE sub-record appears after a header. - // The script data is following after DELE in this case. - mIsDeleted = readDeleSubRecord(esm); - mVarNames.clear(); + mIsDeleted = false; + bool hasHeader = false; while (esm.hasMoreSubs()) { esm.getSubName(); uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'S','C','H','D'>::value: + SCHD data; + esm.getHT(data, 52); + mData = data.mData; + mId = data.mName.toString(); + hasHeader = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'S','C','V','R'>::value: // list of local variables loadSCVR(esm); @@ -98,8 +101,12 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasHeader) + esm.fail("Missing SCHD subrecord"); } void Script::save(ESMWriter &esm) const @@ -119,7 +126,7 @@ namespace ESM if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); } if (!mVarNames.empty()) diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index 261087be0..a20e6ee51 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -3,24 +3,21 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { unsigned int SoundGenerator::sRecordId = REC_SNDG; SoundGenerator::SoundGenerator() - : mIsDeleted(false) + : mType(LeftFoot), + mIsDeleted(false) {} void SoundGenerator::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -28,6 +25,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mType, 4); hasData = true; @@ -40,23 +45,26 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing DATA"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) + esm.fail("Missing DATA subrecord"); } void SoundGenerator::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) - { - writeDeleSubRecord(esm); - return; - } - esm.writeHNT("DATA", mType, 4); esm.writeHNOCString("CNAM", mCreature); esm.writeHNOCString("SNAM", mSound); + + if (mIsDeleted) + { + esm.writeHNCString("DELE", ""); + } } void SoundGenerator::blank() diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 9a1a52b1e..55fe69292 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,12 +14,9 @@ namespace ESM void Sound::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -28,6 +24,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mSound = esm.getHString(); break; @@ -37,18 +41,23 @@ namespace ESM break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing DATA"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) + esm.fail("Missing DATA subrecord"); } void Sound::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index d2d8c7d6d..28feffd20 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -16,13 +15,9 @@ namespace ESM void Spell::load(ESMReader &esm) { mEffects.mList.clear(); + mIsDeleted = false; - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } - + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -31,6 +26,14 @@ namespace ESM switch (val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -43,18 +46,25 @@ namespace ESM esm.getHT(s, 24); mEffects.mList.push_back(s); break; + default: + esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing SPDT subrecord"); } void Spell::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } @@ -70,9 +80,7 @@ namespace ESM mData.mFlags = 0; mName.clear(); - mEffects.mList.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index 2fde46bd2..9a146a370 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,24 +14,46 @@ namespace ESM void Static::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) + mIsDeleted = false; + + bool hasName = false; + while (esm.hasMoreSubs()) { - return; + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + break; + } } - mModel = esm.getHNString("MODL"); + if (!hasName) + esm.fail("Missing NAME subrecord"); } void Static::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); if (mIsDeleted) { - writeDeleSubRecord(esm); - return; + esm.writeHNCString("DELE", ""); + } + else + { + esm.writeHNCString("MODL", mModel); } - - esm.writeHNCString("MODL", mModel); } void Static::blank() diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 38fb94adb..98302c13d 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { @@ -15,12 +14,9 @@ namespace ESM void Weapon::load(ESMReader &esm) { - mId = esm.getHNString("NAME"); - if (mIsDeleted = readDeleSubRecord(esm)) - { - return; - } + mIsDeleted = false; + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { @@ -28,6 +24,14 @@ namespace ESM uint32_t name = esm.retSubName().val; switch (name) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -51,15 +55,19 @@ namespace ESM esm.fail("Unknown subrecord"); } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) esm.fail("Missing WPDT subrecord"); } void Weapon::save(ESMWriter &esm) const { esm.writeHNCString("NAME", mId); + if (mIsDeleted) { - writeDeleSubRecord(esm); + esm.writeHNCString("DELE", ""); return; } From b5c958c62abaa12a488e832a1c6cbf6b588d03c6 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 16 Jul 2015 22:17:49 +0300 Subject: [PATCH 291/441] More ESM records have DELE handling. Changed records: Race, Land, Pathgrid, StartScript, DebugProfile, Filter (cherry picked from commit e65ff723ce6e71da7d00e68820250682512418c1) Conflicts: components/esm/loadland.cpp components/esm/loadland.hpp --- components/esm/debugprofile.cpp | 45 +++- components/esm/debugprofile.hpp | 4 + components/esm/filter.cpp | 41 +++- components/esm/filter.hpp | 4 + components/esm/loadland.cpp | 408 ++++++++++++++------------------ components/esm/loadland.hpp | 4 +- components/esm/loadpgrd.cpp | 186 +++++++++------ components/esm/loadpgrd.hpp | 4 + components/esm/loadrace.cpp | 92 ++++--- components/esm/loadrace.hpp | 4 + components/esm/loadsscr.cpp | 18 ++ components/esm/loadsscr.hpp | 4 + 12 files changed, 473 insertions(+), 341 deletions(-) diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 4f7a92ae6..f927e98e8 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -6,17 +6,53 @@ unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; +ESM::DebugProfile::DebugProfile() + : mIsDeleted(false) +{} + void ESM::DebugProfile::load (ESMReader& esm) { - mId = esm.getHNString ("NAME"); - mDescription = esm.getHNString ("DESC"); - mScriptText = esm.getHNString ("SCRP"); - esm.getHNT (mFlags, "FLAG"); + mIsDeleted = false; + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'S','C','R','P'>::value: + mScriptText = esm.getHString(); + break; + case ESM::FourCC<'F','L','A','G'>::value: + esm.getHT(mFlags); + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } void ESM::DebugProfile::save (ESMWriter& esm) const { esm.writeHNCString ("NAME", mId); + + if (mIsDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString ("DESC", mDescription); esm.writeHNCString ("SCRP", mScriptText); esm.writeHNT ("FLAG", mFlags); @@ -27,4 +63,5 @@ void ESM::DebugProfile::blank() mDescription.clear(); mScriptText.clear(); mFlags = 0; + mIsDeleted = false; } diff --git a/components/esm/debugprofile.hpp b/components/esm/debugprofile.hpp index b54e8ff5f..1709136f5 100644 --- a/components/esm/debugprofile.hpp +++ b/components/esm/debugprofile.hpp @@ -27,6 +27,10 @@ namespace ESM unsigned int mFlags; + bool mIsDeleted; + + DebugProfile(); + void load (ESMReader& esm); void save (ESMWriter& esm) const; diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 01f45030d..39c05924b 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -6,16 +6,50 @@ unsigned int ESM::Filter::sRecordId = REC_FILT; +ESM::Filter::Filter() + : mIsDeleted(false) +{} + void ESM::Filter::load (ESMReader& esm) { - mId = esm.getHNString ("NAME"); - mFilter = esm.getHNString ("FILT"); - mDescription = esm.getHNString ("DESC"); + mIsDeleted = false; + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; + case ESM::FourCC<'F','I','L','T'>::value: + mFilter = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } void ESM::Filter::save (ESMWriter& esm) const { esm.writeHNCString ("NAME", mId); + + if (mIsDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString ("FILT", mFilter); esm.writeHNCString ("DESC", mDescription); } @@ -24,4 +58,5 @@ void ESM::Filter::blank() { mFilter.clear(); mDescription.clear(); + mIsDeleted = false; } diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp index bc3dd7bdc..1a8af9229 100644 --- a/components/esm/filter.hpp +++ b/components/esm/filter.hpp @@ -18,6 +18,10 @@ namespace ESM std::string mFilter; + bool mIsDeleted; + + Filter(); + void load (ESMReader& esm); void save (ESMWriter& esm) const; diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 784cfd407..6033f83cc 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -10,276 +10,234 @@ namespace ESM { unsigned int Land::sRecordId = REC_LAND; -void Land::LandData::save(ESMWriter &esm) const -{ - if (mDataTypes & Land::DATA_VNML) { - esm.writeHNT("VNML", mNormals, sizeof(mNormals)); - } - if (mDataTypes & Land::DATA_VHGT) { - VHGT offsets; - offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; - offsets.mUnk1 = mUnk1; - offsets.mUnk2 = mUnk2; - - float prevY = mHeights[0]; - int number = 0; // avoid multiplication - for (int i = 0; i < LAND_SIZE; ++i) { - float diff = (mHeights[number] - prevY) / HEIGHT_SCALE; - offsets.mHeightData[number] = - (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); - - float prevX = prevY = mHeights[number]; - ++number; - - for (int j = 1; j < LAND_SIZE; ++j) { - diff = (mHeights[number] - prevX) / HEIGHT_SCALE; + void Land::LandData::save(ESMWriter &esm) const + { + if (mDataTypes & Land::DATA_VNML) { + esm.writeHNT("VNML", mNormals, sizeof(mNormals)); + } + if (mDataTypes & Land::DATA_VHGT) { + VHGT offsets; + offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; + offsets.mUnk1 = mUnk1; + offsets.mUnk2 = mUnk2; + + float prevY = mHeights[0]; + int number = 0; // avoid multiplication + for (int i = 0; i < LAND_SIZE; ++i) { + float diff = (mHeights[number] - prevY) / HEIGHT_SCALE; offsets.mHeightData[number] = (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); - prevX = mHeights[number]; + float prevX = prevY = mHeights[number]; ++number; + + for (int j = 1; j < LAND_SIZE; ++j) { + diff = (mHeights[number] - prevX) / HEIGHT_SCALE; + offsets.mHeightData[number] = + (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); + + prevX = mHeights[number]; + ++number; + } } + esm.writeHNT("VHGT", offsets, sizeof(VHGT)); + } + if (mDataTypes & Land::DATA_WNAM) { + esm.writeHNT("WNAM", mWnam, 81); + } + if (mDataTypes & Land::DATA_VCLR) { + esm.writeHNT("VCLR", mColours, 3*LAND_NUM_VERTS); + } + if (mDataTypes & Land::DATA_VTEX) { + static uint16_t vtex[LAND_NUM_TEXTURES]; + transposeTextureData(mTextures, vtex); + esm.writeHNT("VTEX", vtex, sizeof(vtex)); } - esm.writeHNT("VHGT", offsets, sizeof(VHGT)); - } - if (mDataTypes & Land::DATA_WNAM) { - esm.writeHNT("WNAM", mWnam, 81); - } - if (mDataTypes & Land::DATA_VCLR) { - esm.writeHNT("VCLR", mColours, 3*LAND_NUM_VERTS); - } - if (mDataTypes & Land::DATA_VTEX) { - static uint16_t vtex[LAND_NUM_TEXTURES]; - transposeTextureData(mTextures, vtex); - esm.writeHNT("VTEX", vtex, sizeof(vtex)); } -} - -void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) -{ - int readPos = 0; //bit ugly, but it works - for ( int y1 = 0; y1 < 4; y1++ ) - for ( int x1 = 0; x1 < 4; x1++ ) - for ( int y2 = 0; y2 < 4; y2++) - for ( int x2 = 0; x2 < 4; x2++ ) - out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++]; -} - -Land::Land() - : mFlags(0) - , mX(0) - , mY(0) - , mPlugin(0) - , mEsm(NULL) - , mDataTypes(0) - , mDataLoaded(false) - , mLandData(NULL) -{ -} - -Land::~Land() -{ - delete mLandData; -} - -void Land::load(ESMReader &esm) -{ - mEsm = &esm; - mPlugin = mEsm->getIndex(); - - // Get the grid location - esm.getSubNameIs("INTV"); - esm.getSubHeaderIs(8); - esm.getT(mX); - esm.getT(mY); - esm.getHNT(mFlags, "DATA"); - - // Store the file position - mContext = esm.getContext(); - - // Skip these here. Load the actual data when the cell is loaded. - if (esm.isNextSub("VNML")) + void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) { - esm.skipHSubSize(12675); - mDataTypes |= DATA_VNML; - } - if (esm.isNextSub("VHGT")) + int readPos = 0; //bit ugly, but it works + for ( int y1 = 0; y1 < 4; y1++ ) + for ( int x1 = 0; x1 < 4; x1++ ) + for ( int y2 = 0; y2 < 4; y2++) + for ( int x2 = 0; x2 < 4; x2++ ) + out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++]; + } + + Land::Land() + : mFlags(0) + , mX(0) + , mY(0) + , mPlugin(0) + , mEsm(NULL) + , mDataTypes(0) + , mDataLoaded(false) + , mLandData(NULL) + , mIsDeleted(false) { - esm.skipHSubSize(4232); - mDataTypes |= DATA_VHGT; } - if (esm.isNextSub("WNAM")) - { - esm.skipHSubSize(81); - mDataTypes |= DATA_WNAM; - } - if (esm.isNextSub("VCLR")) + + Land::~Land() { - esm.skipHSubSize(12675); - mDataTypes |= DATA_VCLR; + delete mLandData; } - if (esm.isNextSub("VTEX")) + + void Land::load(ESMReader &esm) { - esm.skipHSubSize(512); - mDataTypes |= DATA_VTEX; - } + mEsm = &esm; + mPlugin = mEsm->getIndex(); + mIsDeleted = false; - mDataLoaded = 0; - mLandData = NULL; -} + // Get the grid location + esm.getSubNameIs("INTV"); + esm.getSubHeaderIs(8); + esm.getT(mX); + esm.getT(mY); -void Land::save(ESMWriter &esm) const -{ - esm.startSubRecord("INTV"); - esm.writeT(mX); - esm.writeT(mY); - esm.endRecord("INTV"); + esm.getHNT(mFlags, "DATA"); - esm.writeHNT("DATA", mFlags); -} + if (esm.isNextSub("DELE")) + { + esm.skipHSub(); + mIsDeleted = true; + } -void Land::loadData(int flags) const -{ - // Try to load only available data - flags = flags & mDataTypes; - // Return if all required data is loaded - if ((mDataLoaded & flags) == flags) { - return; - } - // Create storage if nothing is loaded - if (mLandData == NULL) { - mLandData = new LandData; - mLandData->mDataTypes = mDataTypes; - } - mEsm->restoreContext(mContext); + // Store the file position + mContext = esm.getContext(); - if (mEsm->isNextSub("VNML")) { - condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); + // Skip these here. Load the actual data when the cell is loaded. + if (esm.isNextSub("VNML")) + { + esm.skipHSubSize(12675); + mDataTypes |= DATA_VNML; + } + if (esm.isNextSub("VHGT")) + { + esm.skipHSubSize(4232); + mDataTypes |= DATA_VHGT; + } + if (esm.isNextSub("WNAM")) + { + esm.skipHSubSize(81); + mDataTypes |= DATA_WNAM; + } + if (esm.isNextSub("VCLR")) + { + esm.skipHSubSize(12675); + mDataTypes |= DATA_VCLR; + } + if (esm.isNextSub("VTEX")) + { + esm.skipHSubSize(512); + mDataTypes |= DATA_VTEX; + } + + mDataLoaded = 0; + mLandData = NULL; } - if (mEsm->isNextSub("VHGT")) { - static VHGT vhgt; - if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { - float rowOffset = vhgt.mHeightOffset; - for (int y = 0; y < LAND_SIZE; y++) { - rowOffset += vhgt.mHeightData[y * LAND_SIZE]; + void Land::save(ESMWriter &esm) const + { + esm.startSubRecord("INTV"); + esm.writeT(mX); + esm.writeT(mY); + esm.endRecord("INTV"); - mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; + esm.writeHNT("DATA", mFlags); - float colOffset = rowOffset; - for (int x = 1; x < LAND_SIZE; x++) { - colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; - mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; - } - } - mLandData->mUnk1 = vhgt.mUnk1; - mLandData->mUnk2 = vhgt.mUnk2; + if (mIsDeleted) + { + esm.writeHNCString("DELE", ""); } - } - if (mEsm->isNextSub("WNAM")) { - condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); - } - if (mEsm->isNextSub("VCLR")) - condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); - if (mEsm->isNextSub("VTEX")) { - static uint16_t vtex[LAND_NUM_TEXTURES]; - if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { - LandData::transposeTextureData(vtex, mLandData->mTextures); + if (mLandData != NULL) + { + mLandData->save(esm); } } -} -void Land::unloadData() -{ - if (mDataLoaded) + void Land::blank() { - delete mLandData; - mLandData = NULL; - mDataLoaded = 0; + mIsDeleted = false; } -} -bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const -{ - if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { - mEsm->getHExact(ptr, size); - mDataLoaded |= dataFlag; - return true; - } - mEsm->skipHSubSize(size); - return false; -} - -bool Land::isDataLoaded(int flags) const -{ - return (mDataLoaded & flags) == (flags & mDataTypes); -} + void Land::loadData(int flags) const + { + // Try to load only available data + flags = flags & mDataTypes; + // Return if all required data is loaded + if ((mDataLoaded & flags) == flags) { + return; + } + // Create storage if nothing is loaded + if (mLandData == NULL) { + mLandData = new LandData; + mLandData->mDataTypes = mDataTypes; + } + mEsm->restoreContext(mContext); - Land::Land (const Land& land) - : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), - mEsm (land.mEsm), mContext (land.mContext), mDataTypes (land.mDataTypes), - mDataLoaded (land.mDataLoaded), - mLandData (land.mLandData ? new LandData (*land.mLandData) : 0) - {} + if (mEsm->isNextSub("VNML")) { + condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); + } - Land& Land::operator= (Land land) - { - swap (land); - return *this; - } + if (mEsm->isNextSub("VHGT")) { + static VHGT vhgt; + if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { + float rowOffset = vhgt.mHeightOffset; + for (int y = 0; y < LAND_SIZE; y++) { + rowOffset += vhgt.mHeightData[y * LAND_SIZE]; - void Land::swap (Land& land) - { - std::swap (mFlags, land.mFlags); - std::swap (mX, land.mX); - std::swap (mY, land.mY); - std::swap (mPlugin, land.mPlugin); - std::swap (mEsm, land.mEsm); - std::swap (mContext, land.mContext); - std::swap (mDataTypes, land.mDataTypes); - std::swap (mDataLoaded, land.mDataLoaded); - std::swap (mLandData, land.mLandData); - } + mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; - const Land::LandData *Land::getLandData (int flags) const - { - if (!(flags & mDataTypes)) - return 0; + float colOffset = rowOffset; + for (int x = 1; x < LAND_SIZE; x++) { + colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; + mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + } + } + mLandData->mUnk1 = vhgt.mUnk1; + mLandData->mUnk2 = vhgt.mUnk2; + } + } - loadData (flags); - return mLandData; + if (mEsm->isNextSub("WNAM")) { + condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); + } + if (mEsm->isNextSub("VCLR")) + condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); + if (mEsm->isNextSub("VTEX")) { + static uint16_t vtex[LAND_NUM_TEXTURES]; + if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { + LandData::transposeTextureData(vtex, mLandData->mTextures); + } + } } - const Land::LandData *Land::getLandData() const + void Land::unloadData() { - return mLandData; + if (mDataLoaded) + { + delete mLandData; + mLandData = NULL; + mDataLoaded = 0; + } } - Land::LandData *Land::getLandData() + bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const { - return mLandData; + if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { + mEsm->getHExact(ptr, size); + mDataLoaded |= dataFlag; + return true; + } + mEsm->skipHSubSize(size); + return false; } - void Land::add (int flags) + bool Land::isDataLoaded(int flags) const { - if (!mLandData) - mLandData = new LandData; - - mDataTypes |= flags; - mDataLoaded |= flags; + return (mDataLoaded & flags) == (flags & mDataTypes); } - void Land::remove (int flags) - { - mDataTypes &= ~flags; - mDataLoaded &= ~flags; - - if (!mDataLoaded) - { - delete mLandData; - mLandData = 0; - } - } } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 8ec4f74ea..297d54883 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -106,10 +106,12 @@ struct Land static void transposeTextureData(const uint16_t *in, uint16_t *out); }; + bool mIsDeleted; + void load(ESMReader &esm); void save(ESMWriter &esm) const; - void blank() {} + void blank(); /** * Actually loads data diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index fc0974c9d..5e8de9d57 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -32,98 +32,137 @@ namespace ESM { } -void Pathgrid::load(ESMReader &esm) -{ - esm.getHNT(mData, "DATA", 12); - mCell = esm.getHNString("NAME"); + Pathgrid::Pathgrid() + : mIsDeleted(false) + {} - mPoints.clear(); - mEdges.clear(); + void Pathgrid::load(ESMReader &esm) + { + mPoints.clear(); + mEdges.clear(); - // keep track of total connections so we can reserve edge vector size - int edgeCount = 0; + // keep track of total connections so we can reserve edge vector size + int edgeCount = 0; - if (esm.isNextSub("PGRP")) - { - esm.getSubHeader(); - int size = esm.getSubSize(); - // Check that the sizes match up. Size = 16 * s2 (path points) - if (size != static_cast (sizeof(Point) * mData.mS2)) - esm.fail("Path point subrecord size mismatch"); - else + bool hasData = false; + bool hasName = false; + while (esm.hasMoreSubs()) { - int pointCount = mData.mS2; - mPoints.reserve(pointCount); - for (int i = 0; i < pointCount; ++i) + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) { - Point p; - esm.getExact(&p, sizeof(Point)); - mPoints.push_back(p); - edgeCount += p.mConnectionNum; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::FourCC<'N','A','M','E'>::value: + mCell = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'P','G','R','P'>::value: + { + esm.getSubHeader(); + int size = esm.getSubSize(); + // Check that the sizes match up. Size = 16 * s2 (path points) + if (size != static_cast (sizeof(Point) * mData.mS2)) + esm.fail("Path point subrecord size mismatch"); + else + { + int pointCount = mData.mS2; + mPoints.reserve(pointCount); + for (int i = 0; i < pointCount; ++i) + { + Point p; + esm.getExact(&p, sizeof(Point)); + mPoints.push_back(p); + edgeCount += p.mConnectionNum; + } + } + break; + } + case ESM::FourCC<'P','G','R','C'>::value: + { + esm.getSubHeader(); + int size = esm.getSubSize(); + if (size % sizeof(int) != 0) + esm.fail("PGRC size not a multiple of 4"); + else + { + int rawConnNum = size / sizeof(int); + std::vector rawConnections; + rawConnections.reserve(rawConnNum); + for (int i = 0; i < rawConnNum; ++i) + { + int currentValue; + esm.getT(currentValue); + rawConnections.push_back(currentValue); + } + + std::vector::const_iterator rawIt = rawConnections.begin(); + int pointIndex = 0; + mEdges.reserve(edgeCount); + for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex) + { + unsigned char connectionNum = (*it).mConnectionNum; + for (int i = 0; i < connectionNum; ++i) { + Edge edge; + edge.mV0 = pointIndex; + edge.mV1 = *rawIt; + ++rawIt; + mEdges.push_back(edge); + } + } + } + break; + } + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } } + + if (!hasData) + esm.fail("Missing DATA subrecord"); + if (!hasName) + esm.fail("Missing NAME subrecord"); } - if (esm.isNextSub("PGRC")) + void Pathgrid::save(ESMWriter &esm) const { - esm.getSubHeader(); - int size = esm.getSubSize(); - if (size % sizeof(int) != 0) - esm.fail("PGRC size not a multiple of 4"); - else - { - int rawConnNum = size / sizeof(int); - std::vector rawConnections; - rawConnections.reserve(rawConnNum); - for (int i = 0; i < rawConnNum; ++i) - { - int currentValue; - esm.getT(currentValue); - rawConnections.push_back(currentValue); - } + esm.writeHNT("DATA", mData, 12); + esm.writeHNCString("NAME", mCell); - std::vector::const_iterator rawIt = rawConnections.begin(); - int pointIndex = 0; - mEdges.reserve(edgeCount); - for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex) - { - unsigned char connectionNum = (*it).mConnectionNum; - for (int i = 0; i < connectionNum; ++i) { - Edge edge; - edge.mV0 = pointIndex; - edge.mV1 = *rawIt; - ++rawIt; - mEdges.push_back(edge); - } - } + if (mIsDeleted) + { + esm.writeHNCString("DELE", ""); + return; } - } -} -void Pathgrid::save(ESMWriter &esm) const -{ - esm.writeHNT("DATA", mData, 12); - esm.writeHNCString("NAME", mCell); - if (!mPoints.empty()) - { - esm.startSubRecord("PGRP"); - for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) + if (!mPoints.empty()) { - esm.writeT(*it); + esm.startSubRecord("PGRP"); + for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) + { + esm.writeT(*it); + } + esm.endRecord("PGRP"); } - esm.endRecord("PGRP"); - } - if (!mEdges.empty()) - { - esm.startSubRecord("PGRC"); - for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + if (!mEdges.empty()) { - esm.writeT(it->mV1); + esm.startSubRecord("PGRC"); + for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + { + esm.writeT(it->mV1); + } + esm.endRecord("PGRC"); } - esm.endRecord("PGRC"); } -} void Pathgrid::blank() { @@ -134,5 +173,6 @@ void Pathgrid::save(ESMWriter &esm) const mData.mS2 = 0; mPoints.clear(); mEdges.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index f33ccbedf..4b82d9571 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -53,6 +53,10 @@ struct Pathgrid typedef std::vector EdgeList; EdgeList mEdges; + bool mIsDeleted; + + Pathgrid(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index 3feb06c92..12762bda3 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -8,6 +8,10 @@ namespace ESM { unsigned int Race::sRecordId = REC_RACE; + Race::Race() + : mIsDeleted(false) + {} + int Race::MaleFemale::getValue (bool male) const { return male ? mMale : mFemale; @@ -18,47 +22,65 @@ namespace ESM return static_cast(male ? mMale : mFemale); } -void Race::load(ESMReader &esm) -{ - mPowers.mList.clear(); + void Race::load(ESMReader &esm) + { + mPowers.mList.clear(); + mIsDeleted = false; - mId = esm.getHNString("NAME"); + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','A','D','T'>::value: + esm.getHT(mData, 140); + hasData = true; + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mPowers.add(esm); + break; + default: + esm.fail("Unknown subrecord"); + } + } - bool hasData = false; - while (esm.hasMoreSubs()) + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !mIsDeleted) + esm.fail("Missing RADT subrecord"); + } + void Race::save(ESMWriter &esm) const { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + esm.writeHNCString("NAME", mId); + + if (mIsDeleted) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','A','D','T'>::value: - esm.getHT(mData, 140); - hasData = true; - break; - case ESM::FourCC<'D','E','S','C'>::value: - mDescription = esm.getHString(); - break; - case ESM::FourCC<'N','P','C','S'>::value: - mPowers.add(esm); - break; - default: - esm.fail("Unknown subrecord"); + esm.writeHNCString("DELE", ""); + return; } + + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("RADT", mData, 140); + mPowers.save(esm); + esm.writeHNOString("DESC", mDescription); } - if (!hasData) - esm.fail("Missing RADT subrecord"); -} -void Race::save(ESMWriter &esm) const -{ - esm.writeHNCString("NAME", mId); - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("RADT", mData, 140); - mPowers.save(esm); - esm.writeHNOString("DESC", mDescription); -} void Race::blank() { diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index 553d2e68b..e8e9a442b 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -68,6 +68,10 @@ struct Race std::string mId, mName, mDescription; SpellList mPowers; + bool mIsDeleted; + + Race(); + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index 7380dd0a7..076f73742 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -8,8 +8,14 @@ namespace ESM { unsigned int StartScript::sRecordId = REC_SSCR; + StartScript::StartScript() + : mIsDeleted(false) + {} + void StartScript::load(ESMReader &esm) { + mIsDeleted = false; + bool hasData = false; bool hasName = false; while (esm.hasMoreSubs()) @@ -26,10 +32,16 @@ namespace ESM mId = esm.getHString(); hasName = true; break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mIsDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } + if (!hasData) esm.fail("Missing DATA"); if (!hasName) @@ -39,10 +51,16 @@ namespace ESM { esm.writeHNString("DATA", mData); esm.writeHNString("NAME", mId); + + if (mIsDeleted) + { + esm.writeHNCString("DELE", ""); + } } void StartScript::blank() { mData.clear(); + mIsDeleted = false; } } diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index dc7ad6a42..e475abd86 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -26,6 +26,10 @@ struct StartScript std::string mData; std::string mId; + bool mIsDeleted; + + StartScript(); + // Load a record and add it to the list void load(ESMReader &esm); void save(ESMWriter &esm) const; From 696f7a942bb4b0d222967ef0136e3cb0b09b548b Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Thu, 16 Jul 2015 22:31:59 +0300 Subject: [PATCH 292/441] Some refactoring. Remove unused code (cherry picked from commit 5fd48efd28e45336a03f5ee2f5b68f799159650e) Conflicts: apps/openmw/mwworld/store.cpp --- components/esm/loadbsgn.cpp | 1 - components/esm/loadcell.cpp | 258 ++++++++++++++++++------------------ components/esm/loadlock.cpp | 1 + components/esm/loadltex.cpp | 2 +- components/esm/util.cpp | 75 ----------- components/esm/util.hpp | 47 ------- 6 files changed, 134 insertions(+), 250 deletions(-) delete mode 100644 components/esm/util.cpp diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 9f5cd7270..54de009aa 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -3,7 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include "util.hpp" namespace ESM { diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 67701a5b7..7e02f0f85 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -12,7 +12,6 @@ #include "esmwriter.hpp" #include "defs.hpp" #include "cellid.hpp" -#include "util.hpp" namespace { @@ -53,171 +52,178 @@ namespace ESM return ref.mRefNum == refNum; } -void Cell::load(ESMReader &esm, bool saveContext) -{ - loadName(esm); - loadData(esm); - loadCell(esm, saveContext); -} + void Cell::load(ESMReader &esm, bool saveContext) + { + loadName(esm); + loadData(esm); + loadCell(esm, saveContext); + } -void Cell::loadName(ESMReader &esm) -{ - mName = esm.getHNString("NAME"); - mIsDeleted = readDeleSubRecord(esm); -} + void Cell::loadName(ESMReader &esm) + { + mName = esm.getHNString("NAME"); -void Cell::loadCell(ESMReader &esm, bool saveContext) -{ - mRefNumCounter = 0; + mIsDeleted = false; + if (esm.isNextSub("DELE")) + { + esm.skipHSub(); + mIsDeleted = true; + } + } - if (mData.mFlags & Interior) + void Cell::loadCell(ESMReader &esm, bool saveContext) { - // Interior cells - if (esm.isNextSub("INTV")) + mRefNumCounter = 0; + + if (mData.mFlags & Interior) { - int waterl; - esm.getHT(waterl); - mWater = (float) waterl; - mWaterInt = true; + // Interior cells + if (esm.isNextSub("INTV")) + { + int waterl; + esm.getHT(waterl); + mWater = (float) waterl; + mWaterInt = true; + } + else if (esm.isNextSub("WHGT")) + { + esm.getHT(mWater); + } + + // Quasi-exterior cells have a region (which determines the + // weather), pure interior cells have ambient lighting + // instead. + if (mData.mFlags & QuasiEx) + mRegion = esm.getHNOString("RGNN"); + else if (esm.isNextSub("AMBI")) + esm.getHT(mAmbi); } - else if (esm.isNextSub("WHGT")) + else { - esm.getHT(mWater); + // Exterior cells + mRegion = esm.getHNOString("RGNN"); + + mMapColor = 0; + esm.getHNOT(mMapColor, "NAM5"); + } + if (esm.isNextSub("NAM0")) { + esm.getHT(mRefNumCounter); } - // Quasi-exterior cells have a region (which determines the - // weather), pure interior cells have ambient lighting - // instead. - if (mData.mFlags & QuasiEx) - mRegion = esm.getHNOString("RGNN"); - else if (esm.isNextSub("AMBI")) - esm.getHT(mAmbi); + if (saveContext) { + mContextList.push_back(esm.getContext()); + esm.skipRecord(); + } } - else - { - // Exterior cells - mRegion = esm.getHNOString("RGNN"); - mMapColor = 0; - esm.getHNOT(mMapColor, "NAM5"); - } - if (esm.isNextSub("NAM0")) { - esm.getHT(mRefNumCounter); + void Cell::loadData(ESMReader &esm) + { + esm.getHNT(mData, "DATA", 12); } - if (saveContext) { + void Cell::postLoad(ESMReader &esm) + { + // Save position of the cell references and move on mContextList.push_back(esm.getContext()); esm.skipRecord(); } -} -void Cell::loadData(ESMReader &esm) -{ - esm.getHNT(mData, "DATA", 12); -} - -void Cell::postLoad(ESMReader &esm) -{ - // Save position of the cell references and move on - mContextList.push_back(esm.getContext()); - esm.skipRecord(); -} - -void Cell::save(ESMWriter &esm) const -{ - esm.writeHNCString("NAME", mName); - if (mIsDeleted) + void Cell::save(ESMWriter &esm) const { - writeDeleSubRecord(esm); - } + esm.writeHNCString("NAME", mName); - esm.writeHNT("DATA", mData, 12); - if (mData.mFlags & Interior) - { - if (mWaterInt) { - int water = - (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); - esm.writeHNT("INTV", water); - } else { - esm.writeHNT("WHGT", mWater); + if (mIsDeleted) + { + esm.writeHNCString("DELE", ""); } - if (mData.mFlags & QuasiEx) - esm.writeHNOCString("RGNN", mRegion); + esm.writeHNT("DATA", mData, 12); + if (mData.mFlags & Interior) + { + if (mWaterInt) { + int water = + (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); + esm.writeHNT("INTV", water); + } else { + esm.writeHNT("WHGT", mWater); + } + + if (mData.mFlags & QuasiEx) + esm.writeHNOCString("RGNN", mRegion); + else + esm.writeHNT("AMBI", mAmbi, 16); + } else - esm.writeHNT("AMBI", mAmbi, 16); - } - else - { - esm.writeHNOCString("RGNN", mRegion); - if (mMapColor != 0) - esm.writeHNT("NAM5", mMapColor); - } - - if (mRefNumCounter != 0 && !mIsDeleted) - esm.writeHNT("NAM0", mRefNumCounter); -} - -void Cell::restore(ESMReader &esm, int iCtx) const -{ - esm.restoreContext(mContextList.at (iCtx)); -} + { + esm.writeHNOCString("RGNN", mRegion); + if (mMapColor != 0) + esm.writeHNT("NAM5", mMapColor); + } -std::string Cell::getDescription() const -{ - if (mData.mFlags & Interior) - { - return mName; + if (mRefNumCounter != 0) + esm.writeHNT("NAM0", mRefNumCounter); } - else + + void Cell::restore(ESMReader &esm, int iCtx) const { - std::ostringstream stream; - stream << mData.mX << ", " << mData.mY; - return stream.str(); + esm.restoreContext(mContextList.at (iCtx)); } -} -bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool ignoreMoves, MovedCellRef *mref) -{ - // TODO: Try and document reference numbering, I don't think this has been done anywhere else. - if (!esm.hasMoreSubs()) - return false; - - // NOTE: We should not need this check. It is a safety check until we have checked - // more plugins, and how they treat these moved references. - if (esm.isNextSub("MVRF")) + std::string Cell::getDescription() const { - if (ignoreMoves) + if (mData.mFlags & Interior) { - esm.getHT (mref->mRefNum.mIndex); - esm.getHNOT (mref->mTarget, "CNDT"); - adjustRefNum (mref->mRefNum, esm); + return mName; } else { - // skip rest of cell record (moved references), they are handled elsewhere - esm.skipRecord(); // skip MVRF, CNDT - return false; + std::ostringstream stream; + stream << mData.mX << ", " << mData.mY; + return stream.str(); } } - ref.load (esm); + bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool ignoreMoves, MovedCellRef *mref) + { + // TODO: Try and document reference numbering, I don't think this has been done anywhere else. + if (!esm.hasMoreSubs()) + return false; - // Identify references belonging to a parent file and adapt the ID accordingly. - adjustRefNum (ref.mRefNum, esm); + // NOTE: We should not need this check. It is a safety check until we have checked + // more plugins, and how they treat these moved references. + if (esm.isNextSub("MVRF")) + { + if (ignoreMoves) + { + esm.getHT (mref->mRefNum.mIndex); + esm.getHNOT (mref->mTarget, "CNDT"); + adjustRefNum (mref->mRefNum, esm); + } + else + { + // skip rest of cell record (moved references), they are handled elsewhere + esm.skipRecord(); // skip MVRF, CNDT + return false; + } + } - return true; -} + ref.load (esm); -bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) -{ - esm.getHT(mref.mRefNum.mIndex); - esm.getHNOT(mref.mTarget, "CNDT"); + // Identify references belonging to a parent file and adapt the ID accordingly. + adjustRefNum (ref.mRefNum, esm); - adjustRefNum (mref.mRefNum, esm); + return true; + } - return true; -} + bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) + { + esm.getHT(mref.mRefNum.mIndex); + esm.getHNOT(mref.mTarget, "CNDT"); + + adjustRefNum (mref.mRefNum, esm); + + return true; + } void Cell::blank() { diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 5ee041dab..2cfe43743 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -26,6 +26,7 @@ namespace ESM { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); + hasName = true; break; case ESM::FourCC<'D','E','L','E'>::value: esm.skipHSub(); diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index 7c14536ed..6bd48d801 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -26,7 +26,7 @@ namespace ESM { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); - hasName = false; + hasName = true; break; case ESM::FourCC<'D','E','L','E'>::value: esm.skipHSub(); diff --git a/components/esm/util.cpp b/components/esm/util.cpp deleted file mode 100644 index a5ec377a3..000000000 --- a/components/esm/util.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "util.hpp" - -#include - -namespace ESM -{ - bool readDeleSubRecord(ESMReader &esm) - { - if (esm.isNextSub("DELE")) - { - esm.skipHSub(); - return true; - } - return false; - } - - void writeDeleSubRecord(ESMWriter &esm) - { - esm.writeHNT("DELE", static_cast(0)); - } - - template <> - bool isRecordDeleted(const StartScript &script) - { - return false; - } - - template <> - bool isRecordDeleted(const Race &race) - { - return false; - } - - template <> - bool isRecordDeleted(const GameSetting &gmst) - { - return false; - } - - template <> - bool isRecordDeleted(const Skill &skill) - { - return false; - } - - template <> - bool isRecordDeleted(const MagicEffect &mgef) - { - return false; - } - - template <> - bool isRecordDeleted(const Pathgrid &pgrd) - { - return false; - } - - template <> - bool isRecordDeleted(const Land &land) - { - return false; - } - - template <> - bool isRecordDeleted(const DebugProfile &profile) - { - return false; - } - - template <> - bool isRecordDeleted(const Filter &filter) - { - return false; - } -} diff --git a/components/esm/util.hpp b/components/esm/util.hpp index 233232cdd..13a1c6544 100644 --- a/components/esm/util.hpp +++ b/components/esm/util.hpp @@ -8,16 +8,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "loadsscr.hpp" -#include "loadglob.hpp" -#include "loadrace.hpp" -#include "loadgmst.hpp" -#include "loadskil.hpp" -#include "loadmgef.hpp" -#include "loadland.hpp" -#include "loadpgrd.hpp" -#include "debugprofile.hpp" -#include "filter.hpp" namespace ESM { @@ -61,43 +51,6 @@ struct Vector3 } }; -bool readDeleSubRecord(ESMReader &esm); -void writeDeleSubRecord(ESMWriter &esm); - -template -bool isRecordDeleted(const RecordT &record) -{ - return record.mIsDeleted; -} - -// The following records can't be deleted (for now) -template <> -bool isRecordDeleted(const StartScript &script); - -template <> -bool isRecordDeleted(const Race &race); - -template <> -bool isRecordDeleted(const GameSetting &gmst); - -template <> -bool isRecordDeleted(const Skill &skill); - -template <> -bool isRecordDeleted(const MagicEffect &mgef); - -template <> -bool isRecordDeleted(const Pathgrid &pgrd); - -template <> -bool isRecordDeleted(const Land &land); - -template <> -bool isRecordDeleted(const DebugProfile &profile); - -template <> -bool isRecordDeleted(const Filter &filter); - } #endif From 9a803739ff21ed67f49ae28373244c32fb0d7fc8 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 18 Jul 2015 15:55:24 +0300 Subject: [PATCH 293/441] Add a separate method to check whether a record is deleted or not for IdCollection (cherry picked from commit a4d3e59e5c7380ffb27b4d17d4574009e2b45379) --- apps/opencs/model/world/idcollection.cpp | 3 +++ apps/opencs/model/world/idcollection.hpp | 4 ++-- apps/opencs/model/world/land.hpp | 10 ---------- apps/opencs/model/world/pathgrid.hpp | 10 ---------- apps/opencs/model/world/record.cpp | 24 ++++++++++++++++++++++++ apps/opencs/model/world/record.hpp | 23 +++++++++++++++++++++++ 6 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 apps/opencs/model/world/idcollection.cpp diff --git a/apps/opencs/model/world/idcollection.cpp b/apps/opencs/model/world/idcollection.cpp new file mode 100644 index 000000000..9571ed773 --- /dev/null +++ b/apps/opencs/model/world/idcollection.cpp @@ -0,0 +1,3 @@ +#include "idcollection.hpp" + + diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index d08abce5b..4acfdc474 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -2,9 +2,9 @@ #define CSM_WOLRD_IDCOLLECTION_H #include -#include #include "collection.hpp" +#include "record.hpp" namespace CSMWorld { @@ -48,7 +48,7 @@ namespace CSMWorld std::string id = IdAccessorT().getId (record); int index = searchId (id); - if (ESM::isRecordDeleted (record)) + if (isRecordDeleted(record)) { if (index==-1) { diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index 1b79913be..22cedb56d 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -4,7 +4,6 @@ #include #include -#include namespace CSMWorld { @@ -21,13 +20,4 @@ namespace CSMWorld }; } -namespace ESM -{ - template <> - bool isRecordDeleted(const CSMWorld::Land &land) - { - return false; - } -} - #endif diff --git a/apps/opencs/model/world/pathgrid.hpp b/apps/opencs/model/world/pathgrid.hpp index cd5e472c8..7e7b7c3bb 100644 --- a/apps/opencs/model/world/pathgrid.hpp +++ b/apps/opencs/model/world/pathgrid.hpp @@ -5,7 +5,6 @@ #include #include -#include namespace CSMWorld { @@ -27,13 +26,4 @@ namespace CSMWorld }; } -namespace ESM -{ - template <> - bool isRecordDeleted(const CSMWorld::Pathgrid &pgrd) - { - return false; - } -} - #endif diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp index f13a36afc..08a2f5ab2 100644 --- a/apps/opencs/model/world/record.cpp +++ b/apps/opencs/model/world/record.cpp @@ -18,3 +18,27 @@ bool CSMWorld::RecordBase::isModified() const { return mState==State_Modified || mState==State_ModifiedOnly; } + +template<> +bool CSMWorld::isRecordDeleted(const CSMWorld::Land &land) +{ + return land.mLand->mIsDeleted; +} + +template<> +bool CSMWorld::isRecordDeleted(const ESM::GameSetting &setting) +{ + return false; +} + +template<> +bool CSMWorld::isRecordDeleted(const ESM::MagicEffect &effect) +{ + return false; +} + +template<> +bool CSMWorld::isRecordDeleted(const ESM::Skill &skill) +{ + return false; +} diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index 3362f9f96..c43a8b6ca 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -3,6 +3,12 @@ #include +#include +#include +#include + +#include "land.hpp" + namespace CSMWorld { struct RecordBase @@ -154,6 +160,23 @@ namespace CSMWorld mState = State_Erased; } } + + // Not all records can be deleted (may be changed in the future), + // so we need to use a separate method to check whether a record is deleted or not. + template + bool isRecordDeleted(const ESXRecordT &record) + { + return record.mIsDeleted; + } + + template<> + bool isRecordDeleted(const Land &land); + template<> + bool isRecordDeleted(const ESM::GameSetting &setting); + template<> + bool isRecordDeleted(const ESM::MagicEffect &effect); + template<> + bool isRecordDeleted(const ESM::Skill &skill); } #endif From a120bb2b54e16a426817a32c9c997501f65caeb9 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sat, 18 Jul 2015 20:32:10 +0300 Subject: [PATCH 294/441] Delete infos of deleted dialogue when loading a content file (cherry picked from commit e04e32bcffa3c95f7c2a007d5fcf09641fff0b03) --- apps/opencs/model/world/data.cpp | 4 +-- apps/opencs/model/world/infocollection.cpp | 36 ++++++++++++++++++++++ apps/opencs/model/world/infocollection.hpp | 2 ++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ce1c7135f..d6587ada2 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1118,11 +1118,11 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) if (mJournals.tryDelete (record.mId)) { - /// \todo handle info records + mJournalInfos.removeDialogueInfos(record.mId); } else if (mTopics.tryDelete (record.mId)) { - /// \todo handle info records + mTopicInfos.removeDialogueInfos(record.mId); } else { diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index b61718637..e2cfea393 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -187,3 +187,39 @@ CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const s return Range (begin, end); } + +void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId) +{ + std::string id = Misc::StringUtils::lowerCase(dialogueId); + std::vector erasedRecords; + + std::map::const_iterator current = getIdMap().lower_bound(id); + std::map::const_iterator end = getIdMap().end(); + for (; current != end; ++current) + { + Record record = getRecord(current->second); + + if (Misc::StringUtils::ciEqual(dialogueId, record.get().mTopicId)) + { + if (record.mState == RecordBase::State_ModifiedOnly) + { + erasedRecords.push_back(current->second); + } + else + { + record.mState = RecordBase::State_Deleted; + setRecord(current->second, record); + } + } + else + { + break; + } + } + + while (!erasedRecords.empty()) + { + removeRows(erasedRecords.back(), 1); + erasedRecords.pop_back(); + } +} diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 6db47373d..e5a5575c7 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -44,6 +44,8 @@ namespace CSMWorld Range getTopicRange (const std::string& topic) const; ///< Return iterators that point to the beginning and past the end of the range for /// the given topic. + + void removeDialogueInfos(const std::string& dialogueId); }; } From 0277a4e8c4a1f7f15e9c7e61880226437d5cbe12 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 19 Jul 2015 11:42:05 +0300 Subject: [PATCH 295/441] Implement saving of deleted records (cherry picked from commit 8e6a7be6f543a112c6aa3e814f572a9d2839ab15) Conflicts: apps/opencs/model/doc/savingstages.cpp --- apps/opencs/model/doc/savingstages.cpp | 176 ++++++++++--------------- apps/opencs/model/doc/savingstages.hpp | 26 ++-- apps/opencs/model/world/collection.hpp | 6 + apps/opencs/model/world/record.cpp | 24 ++++ apps/opencs/model/world/record.hpp | 16 +++ 5 files changed, 126 insertions(+), 122 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 3fba2cd85..8a17ae031 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -99,41 +99,38 @@ int CSMDoc::WriteDialogueCollectionStage::setup() void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& messages) { + ESM::ESMWriter& writer = mState.getWriter(); const CSMWorld::Record& topic = mTopics.getRecord (stage); - CSMWorld::RecordBase::State state = topic.mState; - - if (state==CSMWorld::RecordBase::State_Deleted) + if (topic.mState == CSMWorld::RecordBase::State_Deleted) { // if the topic is deleted, we do not need to bother with INFO records. + ESM::Dialogue dialogue = topic.get(); + dialogue.mIsDeleted = true; - /// \todo wrote record with delete flag - + writer.startRecord(dialogue.sRecordId); + dialogue.save(writer); + writer.endRecord(dialogue.sRecordId); return; } // Test, if we need to save anything associated info records. bool infoModified = false; - CSMWorld::InfoCollection::Range range = mInfos.getTopicRange (topic.get().mId); for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - CSMWorld::RecordBase::State state = iter->mState; - - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly || - state==CSMWorld::RecordBase::State_Deleted) + if (topic.isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) { infoModified = true; break; } } - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly || - infoModified) + if (topic.isModified() || infoModified) { + ESM::Dialogue dialogue = topic.get(); + if (infoModified && state != CSMWorld::RecordBase::State_Modified && state != CSMWorld::RecordBase::State_ModifiedOnly) { @@ -150,44 +147,40 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message mState.getWriter().endRecord (topic.mModified.sRecordId); } + writer.startRecord (dialogue.sRecordId); + dialogue.save (writer); + writer.endRecord (dialogue.sRecordId); + // write modified selected info records - for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; - ++iter) + for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - CSMWorld::RecordBase::State infoState = iter->mState; - - if (infoState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo wrote record with delete flag - } - else if (infoState==CSMWorld::RecordBase::State_Modified || - infoState==CSMWorld::RecordBase::State_ModifiedOnly) + if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) { ESM::DialInfo info = iter->get(); info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); + info.mIsDeleted = (iter->mState == CSMWorld::RecordBase::State_Deleted); + info.mPrev = ""; if (iter!=range.first) { CSMWorld::InfoCollection::RecordConstIterator prev = iter; --prev; - info.mPrev = - prev->mModified.mId.substr (prev->mModified.mId.find_last_of ('#')+1); + info.mPrev = prev->get().mId.substr (prev->get().mId.find_last_of ('#')+1); } CSMWorld::InfoCollection::RecordConstIterator next = iter; ++next; + info.mNext = ""; if (next!=range.second) { - info.mNext = - next->mModified.mId.substr (next->mModified.mId.find_last_of ('#')+1); + info.mNext = next->get().mId.substr (next->get().mId.find_last_of ('#')+1); } - mState.getWriter().startRecord (info.sRecordId); - mState.getWriter().writeHNCString ("INAM", info.mId); - info.save (mState.getWriter()); - mState.getWriter().endRecord (info.sRecordId); + writer.startRecord (info.sRecordId); + info.save (writer); + writer.endRecord (info.sRecordId); } } } @@ -279,36 +272,35 @@ int CSMDoc::WriteCellCollectionStage::setup() void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& cell = - mDocument.getData().getCells().getRecord (stage); + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& cell = mDocument.getData().getCells().getRecord (stage); std::map >::const_iterator references = mState.getSubRecords().find (Misc::StringUtils::lowerCase (cell.get().mId)); - if (cell.mState==CSMWorld::RecordBase::State_Modified || - cell.mState==CSMWorld::RecordBase::State_ModifiedOnly || + if (cell.isModified() || + cell.mState == CSMWorld::RecordBase::State_Deleted || references!=mState.getSubRecords().end()) { - bool interior = cell.get().mId.substr (0, 1)!="#"; + CSMWorld::Cell cellRecord = cell.get(); + bool interior = cellRecord.mId.substr (0, 1)!="#"; // write cell data - mState.getWriter().startRecord (cell.mModified.sRecordId); - - mState.getWriter().writeHNOCString ("NAME", cell.get().mName); - - ESM::Cell cell2 = cell.get(); + writer.startRecord (cellRecord.sRecordId); if (interior) - cell2.mData.mFlags |= ESM::Cell::Interior; + cellRecord.mData.mFlags |= ESM::Cell::Interior; else { - cell2.mData.mFlags &= ~ESM::Cell::Interior; + cellRecord.mData.mFlags &= ~ESM::Cell::Interior; - std::istringstream stream (cell.get().mId.c_str()); + std::istringstream stream (cellRecord.mId.c_str()); char ignore; - stream >> ignore >> cell2.mData.mX >> cell2.mData.mY; + stream >> ignore >> cellRecord.mData.mX >> cellRecord.mData.mY; } - cell2.save (mState.getWriter()); + + cellRecord.mIsDeleted = (cell.mState == CSMWorld::RecordBase::State_Deleted); + cellRecord.save (writer); // write references if (references!=mState.getSubRecords().end()) @@ -319,24 +311,25 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) const CSMWorld::Record& ref = mDocument.getData().getReferences().getRecord (*iter); - if (ref.mState==CSMWorld::RecordBase::State_Modified || - ref.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (ref.isModified() || ref.mState == CSMWorld::RecordBase::State_Deleted) { + CSMWorld::CellRef refRecord = ref.get(); + // recalculate the ref's cell location std::ostringstream stream; if (!interior) { - std::pair index = ref.get().getCellIndex(); + std::pair index = refRecord.getCellIndex(); stream << "#" << index.first << " " << index.second; } // An empty mOriginalCell is meant to indicate that it is the same as // the current cell. It is possible that a moved ref is moved again. - if ((ref.get().mOriginalCell.empty() ? ref.get().mCell : ref.get().mOriginalCell) + if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) != stream.str() && !interior) { ESM::MovedCellRef moved; - moved.mRefNum = ref.get().mRefNum; + moved.mRefNum = refRecord.mRefNum; // Need to fill mTarget with the ref's new position. std::istringstream istream (stream.str().c_str()); @@ -344,24 +337,17 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) char ignore; istream >> ignore >> moved.mTarget[0] >> moved.mTarget[1]; - ref.get().mRefNum.save (mState.getWriter(), false, "MVRF"); - mState.getWriter().writeHNT ("CNDT", moved.mTarget, 8); + refRecord.mRefNum.save (writer, false, "MVRF"); + writer.writeHNT ("CNDT", moved.mTarget, 8); } - ref.get().save (mState.getWriter()); - } - else if (ref.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + refRecord.mIsDeleted = (ref.mState == CSMWorld::RecordBase::State_Deleted); + refRecord.save (writer); } } } - mState.getWriter().endRecord (cell.mModified.sRecordId); - } - else if (cell.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.endRecord (cellRecord.sRecordId); } } @@ -378,11 +364,11 @@ int CSMDoc::WritePathgridCollectionStage::setup() void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& pathgrid = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& pathgrid = mDocument.getData().getPathgrids().getRecord (stage); - if (pathgrid.mState==CSMWorld::RecordBase::State_Modified || - pathgrid.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (pathgrid.isModified() || pathgrid.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::Pathgrid record = pathgrid.get(); @@ -395,15 +381,10 @@ void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& message else record.mCell = record.mId; - mState.getWriter().startRecord (record.sRecordId); - - record.save (mState.getWriter()); - - mState.getWriter().endRecord (record.sRecordId); - } - else if (pathgrid.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + record.mIsDeleted = (pathgrid.mState == CSMWorld::RecordBase::State_Deleted); + writer.startRecord (record.sRecordId); + record.save (writer); + writer.endRecord (record.sRecordId); } } @@ -420,26 +401,18 @@ int CSMDoc::WriteLandCollectionStage::setup() void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& land = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& land = mDocument.getData().getLand().getRecord (stage); - if (land.mState==CSMWorld::RecordBase::State_Modified || - land.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (land.isModified() || land.mState == CSMWorld::RecordBase::State_Deleted) { const CSMWorld::Land& record = land.get(); - mState.getWriter().startRecord (record.sRecordId); - - record.save (mState.getWriter()); - - if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes)) - data->save (mState.getWriter()); - - mState.getWriter().endRecord (record.sRecordId); - } - else if (land.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + record.mLand->mIsDeleted = (land.mState == CSMWorld::RecordBase::State_Deleted); + writer.startRecord (record.mLand->sRecordId); + record.mLand->save (writer); + writer.endRecord (record.mLand->sRecordId); } } @@ -456,25 +429,18 @@ int CSMDoc::WriteLandTextureCollectionStage::setup() void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& landTexture = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& landTexture = mDocument.getData().getLandTextures().getRecord (stage); - if (landTexture.mState==CSMWorld::RecordBase::State_Modified || - landTexture.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (landTexture.isModified() || landTexture.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::LandTexture record = landTexture.get(); + record.mIsDeleted = (landTexture.mState == CSMWorld::RecordBase::State_Deleted); - mState.getWriter().startRecord (record.sRecordId); - - mState.getWriter().writeHNString("NAME", record.mId); - - record.save (mState.getWriter()); - - mState.getWriter().endRecord (record.sRecordId); - } - else if (landTexture.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.startRecord (record.sRecordId); + record.save (writer); + writer.endRecord (record.sRecordId); } } diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 188f22f96..9dbb5bee3 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -100,26 +100,18 @@ namespace CSMDoc if (CSMWorld::getScopeFromId (mCollection.getRecord (stage).get().mId)!=mScope) return; + ESM::ESMWriter& writer = mState.getWriter(); CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; + CollectionT::ESXRecord record = mCollection.getRecord (stage).get(); - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + if (state == CSMWorld::RecordBase::State_Modified || + state == CSMWorld::RecordBase::State_ModifiedOnly || + state == CSMWorld::RecordBase::State_Deleted) { - // FIXME: A quick Workaround to support records which should not write - // NAME, including SKIL, MGEF and SCPT. If there are many more - // idcollection records that doesn't use NAME then a more generic - // solution may be required. - uint32_t name = mCollection.getRecord (stage).mModified.sRecordId; - mState.getWriter().startRecord (name); - - if(name != ESM::REC_SKIL && name != ESM::REC_MGEF && name != ESM::REC_SCPT) - mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage)); - mCollection.getRecord (stage).mModified.save (mState.getWriter()); - mState.getWriter().endRecord (mCollection.getRecord (stage).mModified.sRecordId); - } - else if (state==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + CSMWorld::setRecordDeleted (record, state == CSMWorld::RecordBase::State_Deleted); + writer.startRecord (record.sRecordId); + record.save (writer); + writer.endRecord (record.sRecordId); } } diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index b0571bbed..16f5ce51f 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -43,6 +43,12 @@ namespace CSMWorld template > class Collection : public CollectionBase { + public: + + typedef ESXRecordT ESXRecord; + + private: + std::vector > mRecords; std::map mIndex; std::vector *> mColumns; diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp index 08a2f5ab2..699d68ba7 100644 --- a/apps/opencs/model/world/record.cpp +++ b/apps/opencs/model/world/record.cpp @@ -42,3 +42,27 @@ bool CSMWorld::isRecordDeleted(const ESM::Skill &skill) { return false; } + +template<> +void CSMWorld::setRecordDeleted(CSMWorld::Land &land, bool isDeleted) +{ + land.mLand->mIsDeleted = isDeleted; +} + +template<> +void CSMWorld::setRecordDeleted(ESM::GameSetting &setting, bool isDeleted) +{ + // GameSetting doesn't have a Deleted flag +} + +template<> +void CSMWorld::setRecordDeleted(ESM::MagicEffect &effect, bool isDeleted) +{ + // MagicEffect doesn't have a Deleted flag +} + +template<> +void CSMWorld::setRecordDeleted(ESM::Skill &skill, bool isDeleted) +{ + // Skill doesn't have a Deleted flag +} diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index c43a8b6ca..8e39cd705 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -177,6 +177,22 @@ namespace CSMWorld bool isRecordDeleted(const ESM::MagicEffect &effect); template<> bool isRecordDeleted(const ESM::Skill &skill); + + // ... and also a separate method for setting the deleted flag of a record + template + void setRecordDeleted(ESXRecordT &record, bool isDeleted = false) + { + record.mIsDeleted = isDeleted; + } + + template<> + void setRecordDeleted(Land &land, bool isDeleted); + template<> + void setRecordDeleted(ESM::GameSetting &setting, bool isDeleted); + template<> + void setRecordDeleted(ESM::MagicEffect &effect, bool isDeleted); + template<> + void setRecordDeleted(ESM::Skill &skill, bool isDeleted); } #endif From 459fe3da52be1742d268fbd37caaaf82ef52a2c5 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 19 Jul 2015 14:55:54 +0300 Subject: [PATCH 296/441] Rework EsmTool code. Remove explicit NAME handling (cherry picked from commit ede4bfcf52a77f371058e0fdce655534d6879056) Conflicts: apps/esmtool/record.cpp --- apps/esmtool/esmtool.cpp | 93 +++++++++++++++++----------------------- apps/esmtool/record.cpp | 75 +++++++++++++++++++++++++++++++- apps/esmtool/record.hpp | 18 +++++--- 3 files changed, 124 insertions(+), 62 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index eef970d37..abb05f3fc 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -250,8 +250,7 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) ESM::CellRef ref; if(!quiet) std::cout << " References:\n"; - bool deleted = false; - while(cell.getNextRef(esm, ref, deleted)) + while(cell.getNextRef(esm, ref)) { if (save) { info.data.mCellRefs[&cell].push_back(ref); @@ -269,7 +268,7 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) std::cout << " Uses/health: '" << ref.mChargeInt << "'\n"; std::cout << " Gold value: '" << ref.mGoldValue << "'\n"; std::cout << " Blocked: '" << static_cast(ref.mReferenceBlocked) << "'" << std::endl; - std::cout << " Deleted: " << deleted << std::endl; + std::cout << " Deleted: " << ref.mIsDeleted << std::endl; if (!ref.mKey.empty()) std::cout << " Key: '" << ref.mKey << "'" << std::endl; } @@ -351,61 +350,58 @@ int load(Arguments& info) uint32_t flags; esm.getRecHeader(flags); + EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); + if (record == 0) + { + if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end()) + { + std::cout << "Skipping " << n.toString() << " records." << std::endl; + skipped.push_back(n.val); + } + + esm.skipRecord(); + if (quiet) break; + std::cout << " Skipping\n"; + + continue; + } + + record->setFlags(static_cast(flags)); + record->setPrintPlain(info.plain_given); + record->load(esm); + // Is the user interested in this record type? bool interested = true; if (!info.types.empty()) { std::vector::iterator match; - match = std::find(info.types.begin(), info.types.end(), - n.toString()); + match = std::find(info.types.begin(), info.types.end(), n.toString()); if (match == info.types.end()) interested = false; } - std::string id = esm.getHNOString("NAME"); - if (id.empty()) - id = esm.getHNOString("INAM"); - - if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, id)) + if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId())) interested = false; if(!quiet && interested) - std::cout << "\nRecord: " << n.toString() - << " '" << id << "'\n"; - - EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); - - if (record == 0) { - if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end()) - { - std::cout << "Skipping " << n.toString() << " records." << std::endl; - skipped.push_back(n.val); - } + { + std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n"; + record->print(); + } - esm.skipRecord(); - if (quiet) break; - std::cout << " Skipping\n"; - } else { - if (record->getType().val == ESM::REC_GMST) { - // preset id for GameSetting record - record->cast()->get().mId = id; - } - record->setId(id); - record->setFlags((int) flags); - record->setPrintPlain(info.plain_given); - record->load(esm); - if (!quiet && interested) record->print(); - - if (record->getType().val == ESM::REC_CELL && loadCells && interested) { - loadCell(record->cast()->get(), esm, info); - } + if (record->getType().val == ESM::REC_CELL && loadCells && interested) + { + loadCell(record->cast()->get(), esm, info); + } - if (save) { - info.data.mRecords.push_back(record); - } else { - delete record; - } - ++info.data.mRecordStats[n.val]; + if (save) + { + info.data.mRecords.push_back(record); + } + else + { + delete record; } + ++info.data.mRecordStats[n.val]; } } catch(std::exception &e) { @@ -492,20 +488,11 @@ int clone(Arguments& info) for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it) { EsmTool::RecordBase *record = *it; - name.val = record->getType().val; esm.startRecord(name.toString(), record->getFlags()); - // TODO wrap this with std::set - if (ESMData::sLabeledRec.count(name.val) > 0) { - esm.writeHNCString("NAME", record->getId()); - } else { - esm.writeHNOString("NAME", record->getId()); - } - record->save(esm); - if (name.val == ESM::REC_CELL) { ESM::Cell *ptr = &record->cast()->get(); if (!info.data.mCellRefs[ptr].empty()) { diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 5fe6471b0..ebb18c625 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -405,6 +405,7 @@ void Record::print() std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -419,6 +420,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -447,6 +449,7 @@ void Record::print() if (pit->mFemale != "") std::cout << " Female Name: " << pit->mFemale << std::endl; } + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -461,6 +464,7 @@ void Record::print() std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -474,6 +478,7 @@ void Record::print() std::cout << " Part: " << meshPartLabel(mData.mData.mPart) << " (" << (int)mData.mData.mPart << ")" << std::endl; std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -502,6 +507,7 @@ void Record::print() { std::cout << " Text: [skipped]" << std::endl; } + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -513,6 +519,7 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit) std::cout << " Power: " << *pit << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -541,6 +548,7 @@ void Record::print() std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl; std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } @@ -563,6 +571,7 @@ void Record::print() for (int i = 0; i != 5; i++) std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1]) << " (" << mData.mData.mSkills[i][1] << ")" << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -589,6 +598,7 @@ void Record::print() if (pit->mFemale != "") std::cout << " Female Name: " << pit->mFemale << std::endl; } + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -604,6 +614,7 @@ void Record::print() for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit) std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount << " Item: " << cit->mItem.toString() << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -670,6 +681,7 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -677,6 +689,7 @@ void Record::print() { std::cout << " Type: " << dialogTypeLabel(mData.mType) << " (" << (int)mData.mType << ")" << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; // Sadly, there are no DialInfos, because the loader dumps as it // loads, rather than loading and then dumping. :-( Anyone mind if // I change this? @@ -693,6 +706,7 @@ void Record::print() std::cout << " Script: " << mData.mScript << std::endl; std::cout << " OpenSound: " << mData.mOpenSound << std::endl; std::cout << " CloseSound: " << mData.mCloseSound << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -704,6 +718,7 @@ void Record::print() std::cout << " Charge: " << mData.mData.mCharge << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -737,12 +752,14 @@ void Record::print() std::map::iterator rit; for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit) std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " " << mData.mValue << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -809,6 +826,7 @@ void Record::print() std::cout << " Result Script: [skipped]" << std::endl; } } + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -832,6 +850,7 @@ void Record::print() std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i]) << " (" << mData.mData.mAttributes[i] << ")" << std::endl; } + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -848,6 +867,7 @@ void Record::print() std::cout << " Unknown1: " << data->mUnk1 << std::endl; std::cout << " Unknown2: " << data->mUnk2 << std::endl; } + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -860,6 +880,7 @@ void Record::print() for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Creature: Level: " << iit->mLevel << " Creature: " << iit->mId << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -872,6 +893,7 @@ void Record::print() for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Inventory: Level: " << iit->mLevel << " Item: " << iit->mId << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -892,6 +914,7 @@ void Record::print() std::cout << " Duration: " << mData.mData.mTime << std::endl; std::cout << " Radius: " << mData.mData.mRadius << std::endl; std::cout << " Color: " << mData.mData.mColor << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -906,6 +929,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -920,6 +944,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -934,6 +959,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -942,6 +968,7 @@ void Record::print() std::cout << " Id: " << mData.mId << std::endl; std::cout << " Index: " << mData.mIndex << std::endl; std::cout << " Texture: " << mData.mTexture << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -992,6 +1019,7 @@ void Record::print() std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Is Key: " << mData.mData.mIsKey << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -1077,6 +1105,8 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); + + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -1111,6 +1141,8 @@ void Record::print() std::cout << " BAD POINT IN EDGE!" << std::endl; i++; } + + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -1151,6 +1183,8 @@ void Record::print() std::vector::iterator sit; for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit) std::cout << " Power: " << *sit << std::endl; + + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -1210,6 +1244,8 @@ void Record::print() { std::cout << " Script: [skipped]" << std::endl; } + + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -1233,6 +1269,7 @@ void Record::print() std::cout << " Sound: " << mData.mSound << std::endl; std::cout << " Type: " << soundTypeLabel(mData.mType) << " (" << mData.mType << ")" << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -1243,6 +1280,7 @@ void Record::print() if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0) std::cout << " Range: " << (int)mData.mData.mMinRange << " - " << (int)mData.mData.mMaxRange << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -1254,13 +1292,15 @@ void Record::print() std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl; std::cout << " Cost: " << mData.mData.mCost << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> void Record::print() { - std::cout << "Start Script: " << mData.mId << std::endl; - std::cout << "Start Data: " << mData.mData << std::endl; + std::cout << " Start Script: " << mData.mId << std::endl; + std::cout << " Start Data: " << mData.mData << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; } template<> @@ -1301,6 +1341,37 @@ void Record::print() if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0) std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-" << (int)mData.mData.mThrust[1] << std::endl; + std::cout << " Deleted: " << mData.mIsDeleted << std::endl; +} + +template<> +std::string Record::getId() const +{ + return mData.mName; +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Land record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for MagicEffect record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Pathgrid record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Skill record } } // end namespace diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index 5e03c64db..c55b2ba8f 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -32,13 +32,7 @@ namespace EsmTool virtual ~RecordBase() {} - const std::string &getId() const { - return mId; - } - - void setId(const std::string &id) { - mId = id; - } + virtual std::string getId() const = 0; uint32_t getFlags() const { return mFlags; @@ -75,6 +69,10 @@ namespace EsmTool T mData; public: + std::string getId() const { + return mData.mId; + } + T &get() { return mData; } @@ -89,6 +87,12 @@ namespace EsmTool void print(); }; + + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; template<> void Record::print(); template<> void Record::print(); From a3d48fd482ba463790baf27b29a3593ccd4becf2 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 19 Jul 2015 16:07:56 +0300 Subject: [PATCH 297/441] Rework ESS importer code. Remove explicit NAME handling for ESM records (cherry picked from commit 6b21da7f8e6b50de71047248c36d4ee9ec51751e) --- apps/essimporter/converter.cpp | 6 ++---- apps/essimporter/converter.hpp | 36 +++++++++++++--------------------- apps/essimporter/importer.cpp | 2 +- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 91d290f33..5c4c0e62e 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -134,8 +134,6 @@ namespace ESSImport void ConvertCell::read(ESM::ESMReader &esm) { ESM::Cell cell; - std::string id = esm.getHNString("NAME"); - cell.mName = id; cell.load(esm, false); // I wonder what 0x40 does? @@ -145,7 +143,7 @@ namespace ESSImport } // note if the player is in a nameless exterior cell, we will assign the cellId later based on player position - if (id == mContext->mPlayerCellName) + if (cell.mName == mContext->mPlayerCellName) { mContext->mPlayer.mCellId = cell.getCellId(); } @@ -253,7 +251,7 @@ namespace ESSImport if (cell.isExterior()) mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; else - mIntCells[id] = newcell; + mIntCells[cell.mName] = newcell; } void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 5711e6754..80d5899ec 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -75,10 +75,9 @@ public: virtual void read(ESM::ESMReader& esm) { - std::string id = esm.getHNString("NAME"); T record; record.load(esm); - mRecords[id] = record; + mRecords[record.mId] = record; } virtual void write(ESM::ESMWriter& esm) @@ -86,7 +85,6 @@ public: for (typename std::map::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it) { esm.startRecord(T::sRecordId); - esm.writeHNString("NAME", it->first); it->second.save(esm); esm.endRecord(T::sRecordId); } @@ -102,14 +100,13 @@ public: virtual void read(ESM::ESMReader &esm) { ESM::NPC npc; - std::string id = esm.getHNString("NAME"); npc.load(esm); - if (id != "player") + if (npc.mId != "player") { // Handles changes to the NPC struct, but since there is no index here // it will apply to ALL instances of the class. seems to be the reason for the // "feature" in MW where changing AI settings of one guard will change it for all guards of that refID. - mContext->mNpcs[Misc::StringUtils::lowerCase(id)] = npc; + mContext->mNpcs[Misc::StringUtils::lowerCase(npc.mId)] = npc; } else { @@ -139,9 +136,8 @@ public: { // See comment in ConvertNPC ESM::Creature creature; - std::string id = esm.getHNString("NAME"); creature.load(esm); - mContext->mCreatures[Misc::StringUtils::lowerCase(id)] = creature; + mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature; } }; @@ -154,18 +150,17 @@ class ConvertGlobal : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Global global; global.load(esm); - if (Misc::StringUtils::ciEqual(id, "gamehour")) + if (Misc::StringUtils::ciEqual(global.mId, "gamehour")) mContext->mHour = global.mValue.getFloat(); - if (Misc::StringUtils::ciEqual(id, "day")) + if (Misc::StringUtils::ciEqual(global.mId, "day")) mContext->mDay = global.mValue.getInteger(); - if (Misc::StringUtils::ciEqual(id, "month")) + if (Misc::StringUtils::ciEqual(global.mId, "month")) mContext->mMonth = global.mValue.getInteger(); - if (Misc::StringUtils::ciEqual(id, "year")) + if (Misc::StringUtils::ciEqual(global.mId, "year")) mContext->mYear = global.mValue.getInteger(); - mRecords[id] = global; + mRecords[global.mId] = global; } }; @@ -174,14 +169,13 @@ class ConvertClass : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Class class_; class_.load(esm); - if (id == "NEWCLASSID_CHARGEN") + if (class_.mId == "NEWCLASSID_CHARGEN") mContext->mCustomPlayerClassName = class_.mName; - mRecords[id] = class_; + mRecords[class_.mId] = class_; } }; @@ -190,13 +184,12 @@ class ConvertBook : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Book book; book.load(esm); if (book.mData.mSkillID == -1) - mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(id)); + mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId)); - mRecords[id] = book; + mRecords[book.mId] = book; } }; @@ -368,11 +361,10 @@ class ConvertFACT : public Converter public: virtual void read(ESM::ESMReader& esm) { - std::string id = esm.getHNString("NAME"); ESM::Faction faction; faction.load(esm); + std::string id = Misc::StringUtils::toLower(faction.mId); - Misc::StringUtils::toLower(id); for (std::map::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) { std::string faction2 = Misc::StringUtils::lowerCase(it->first); diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index b9468ed87..57c4ba4c3 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -348,7 +348,7 @@ namespace ESSImport } writer.startRecord(ESM::REC_NPC_); - writer.writeHNString("NAME", "player"); + context.mPlayerBase.mId = "player"; context.mPlayerBase.save(writer); writer.endRecord(ESM::REC_NPC_); From d12f784e7ea77e8f68c027cf55b8d4d0d546ea36 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 19 Jul 2015 22:50:42 +0300 Subject: [PATCH 298/441] Remove include file from loaddial.cpp (cherry picked from commit f5745749a6e7875c487ac417dead00446af805c2) --- components/esm/loaddial.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index c517dc722..74a708805 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -2,8 +2,6 @@ #include -#include - #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" From 7013ba751c0693050ee853a2e31011625422449d Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 19 Jul 2015 23:27:51 +0300 Subject: [PATCH 299/441] Fix build errors & warnings (cherry picked from commit 1e8182220ae7c14de13ab634c82f2b14444c0e69) --- apps/opencs/model/doc/savingstages.hpp | 2 +- apps/opencs/model/world/idcollection.hpp | 8 ++++---- components/esm/cellref.cpp | 6 ++++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 9dbb5bee3..a7d9704b0 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -102,7 +102,7 @@ namespace CSMDoc ESM::ESMWriter& writer = mState.getWriter(); CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; - CollectionT::ESXRecord record = mCollection.getRecord (stage).get(); + typename CollectionT::ESXRecord record = mCollection.getRecord (stage).get(); if (state == CSMWorld::RecordBase::State_Modified || state == CSMWorld::RecordBase::State_ModifiedOnly || diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 4acfdc474..9d3ec990e 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -46,7 +46,7 @@ namespace CSMWorld loadRecord (record, reader); std::string id = IdAccessorT().getId (record); - int index = searchId (id); + int index = this->searchId (id); if (isRecordDeleted(record)) { @@ -60,13 +60,13 @@ namespace CSMWorld if (base) { - removeRows (index, 1); + this->removeRows (index, 1); return -1; } - Record baseRecord = getRecord (index); + Record baseRecord = this->getRecord (index); baseRecord.mState = RecordBase::State_Deleted; - setRecord (index, baseRecord); + this->setRecord (index, baseRecord); return index; } diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index c4fc93ff5..ceb88b3a7 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -6,10 +6,12 @@ ESM::CellRef::CellRef() : mScale(1.0f), mFactionRank(-2), - mEnchantmentCharge(-1), - mGoldValue(1), mChargeInt(-1), + mEnchantmentCharge(-1.0f), + mGoldValue(1), + mTeleport(false), mLockLevel(0), + mReferenceBlocked(-1), mIsDeleted(false) {} From 82363bf3181a20a9b49e20c0f2ecf2f8cb92108e Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Mon, 20 Jul 2015 17:23:14 +0300 Subject: [PATCH 300/441] Make deleted flag a parameter of load/save methods (instead of a record member) in ESM records (cherry picked from commit 4a16eba716d6795bb9aadc492c4417d1a05828ef) Conflicts: components/esm/loadland.cpp components/esm/loadland.hpp --- components/esm/cellref.cpp | 93 +++++++++------------- components/esm/cellref.hpp | 12 ++- components/esm/debugprofile.cpp | 24 +++--- components/esm/debugprofile.hpp | 8 +- components/esm/esmreader.cpp | 1 + components/esm/filter.cpp | 21 ++--- components/esm/filter.hpp | 8 +- components/esm/loadacti.cpp | 24 +++--- components/esm/loadacti.hpp | 8 +- components/esm/loadalch.cpp | 27 +++---- components/esm/loadalch.hpp | 8 +- components/esm/loadappa.cpp | 26 +++--- components/esm/loadappa.hpp | 8 +- components/esm/loadarmo.cpp | 27 +++---- components/esm/loadarmo.hpp | 8 +- components/esm/loadbody.cpp | 27 +++---- components/esm/loadbody.hpp | 8 +- components/esm/loadbook.cpp | 26 +++--- components/esm/loadbook.hpp | 8 +- components/esm/loadbsgn.cpp | 25 +++--- components/esm/loadbsgn.hpp | 8 +- components/esm/loadcell.cpp | 136 +++++++++++++++++++------------- components/esm/loadcell.hpp | 20 ++--- components/esm/loadclas.cpp | 27 +++---- components/esm/loadclas.hpp | 8 +- components/esm/loadclot.cpp | 27 +++---- components/esm/loadclot.hpp | 8 +- components/esm/loadcont.cpp | 31 +++----- components/esm/loadcont.hpp | 8 +- components/esm/loadcrea.cpp | 31 +++----- components/esm/loadcrea.hpp | 8 +- components/esm/loaddial.cpp | 64 +++++++-------- components/esm/loaddial.hpp | 16 ++-- components/esm/loaddoor.cpp | 24 +++--- components/esm/loaddoor.hpp | 8 +- components/esm/loadench.cpp | 27 +++---- components/esm/loadench.hpp | 8 +- components/esm/loadfact.cpp | 28 +++---- components/esm/loadfact.hpp | 8 +- components/esm/loadglob.cpp | 16 ++-- components/esm/loadglob.hpp | 8 +- components/esm/loadgmst.cpp | 6 +- components/esm/loadgmst.hpp | 4 +- components/esm/loadinfo.cpp | 32 +++----- components/esm/loadinfo.hpp | 10 +-- components/esm/loadingr.cpp | 27 +++---- components/esm/loadingr.hpp | 8 +- components/esm/loadland.cpp | 109 ++++++++++++++----------- components/esm/loadland.hpp | 8 +- components/esm/loadlevlist.cpp | 41 ++++++---- components/esm/loadlevlist.hpp | 8 +- components/esm/loadligh.cpp | 26 +++--- components/esm/loadligh.hpp | 8 +- components/esm/loadlock.cpp | 26 +++--- components/esm/loadlock.hpp | 8 +- components/esm/loadltex.cpp | 24 +++--- components/esm/loadltex.hpp | 8 +- components/esm/loadmgef.cpp | 9 ++- components/esm/loadmgef.hpp | 4 +- components/esm/loadmisc.cpp | 26 +++--- components/esm/loadmisc.hpp | 8 +- components/esm/loadnpc.cpp | 31 +++----- components/esm/loadnpc.hpp | 8 +- components/esm/loadpgrd.cpp | 18 ++--- components/esm/loadpgrd.hpp | 8 +- components/esm/loadprob.cpp | 26 +++--- components/esm/loadprob.hpp | 8 +- components/esm/loadrace.cpp | 26 +++--- components/esm/loadrace.hpp | 8 +- components/esm/loadregn.cpp | 24 ++---- components/esm/loadregn.hpp | 8 +- components/esm/loadrepa.cpp | 26 +++--- components/esm/loadrepa.hpp | 8 +- components/esm/loadscpt.cpp | 26 +++--- components/esm/loadscpt.hpp | 8 +- components/esm/loadskil.cpp | 9 ++- components/esm/loadskil.hpp | 4 +- components/esm/loadsndg.cpp | 27 +++---- components/esm/loadsndg.hpp | 8 +- components/esm/loadsoun.cpp | 27 +++---- components/esm/loadsoun.hpp | 8 +- components/esm/loadspel.cpp | 28 +++---- components/esm/loadspel.hpp | 8 +- components/esm/loadsscr.cpp | 18 ++--- components/esm/loadsscr.hpp | 8 +- components/esm/loadstat.cpp | 24 +++--- components/esm/loadstat.hpp | 8 +- components/esm/loadweap.cpp | 27 +++---- components/esm/loadweap.hpp | 8 +- components/esm/objectstate.cpp | 3 +- 90 files changed, 714 insertions(+), 1050 deletions(-) diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index ceb88b3a7..a61a16ca4 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -3,18 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -ESM::CellRef::CellRef() - : mScale(1.0f), - mFactionRank(-2), - mChargeInt(-1), - mEnchantmentCharge(-1.0f), - mGoldValue(1), - mTeleport(false), - mLockLevel(0), - mReferenceBlocked(-1), - mIsDeleted(false) -{} - void ESM::RefNum::load (ESMReader& esm, bool wide) { if (wide) @@ -36,10 +24,37 @@ void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const } -void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) +void ESM::CellRef::clearData() +{ + mScale = 1; + mOwner.clear(); + mGlobalVariable.clear(); + mSoul.clear(); + mFaction.clear(); + mFactionRank = -2; + mChargeInt = -1; + mEnchantmentCharge = -1; + mGoldValue = 0; + mDestCell.clear(); + mLockLevel = 0; + mKey.clear(); + mTrap.clear(); + mReferenceBlocked = -1; + mTeleport = false; + + for (int i=0; i<3; ++i) + { + mDoorDest.pos[i] = 0; + mDoorDest.rot[i] = 0; + mPos.pos[i] = 0; + mPos.rot[i] = 0; + } +} + +void ESM::CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum) { loadId(esm, wideRefNum); - loadData(esm); + loadData(esm, isDeleted); } void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) @@ -54,27 +69,19 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) mRefNum.load (esm, wideRefNum); mRefID = esm.getHNString ("NAME"); - mIsDeleted = false; } -void ESM::CellRef::loadData(ESMReader &esm) +void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) { - mScale = 1.0f; - mFactionRank = -2; - mChargeInt = -1; - mEnchantmentCharge = -1; - mGoldValue = 1; - mLockLevel = 0; - mReferenceBlocked = -1; - mTeleport = false; - mIsDeleted = false; + isDeleted = false; + + clearData(); bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'U','N','A','M'>::value: esm.getHT(mReferenceBlocked); @@ -130,7 +137,7 @@ void ESM::CellRef::loadData(ESMReader &esm) break; case ESM::FourCC<'D','E','L','E'>::value: esm.skipHSub(); - mIsDeleted = true; + isDeleted = true; break; default: esm.cacheSubName(); @@ -140,7 +147,7 @@ void ESM::CellRef::loadData(ESMReader &esm) } } -void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const +void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool isDeleted) const { mRefNum.save (esm, wideRefNum); @@ -191,7 +198,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons if (!inInventory) esm.writeHNT("DATA", mPos, 24); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); } @@ -201,31 +208,7 @@ void ESM::CellRef::blank() { mRefNum.unset(); mRefID.clear(); - mScale = 1; - mOwner.clear(); - mGlobalVariable.clear(); - mSoul.clear(); - mFaction.clear(); - mFactionRank = -2; - mChargeInt = -1; - mEnchantmentCharge = -1; - mGoldValue = 0; - mDestCell.clear(); - mLockLevel = 0; - mKey.clear(); - mTrap.clear(); - mReferenceBlocked = -1; - mTeleport = false; - - for (int i=0; i<3; ++i) - { - mDoorDest.pos[i] = 0; - mDoorDest.rot[i] = 0; - mPos.pos[i] = 0; - mPos.rot[i] = 0; - } - - mIsDeleted = false; + clearData(); } bool ESM::operator== (const RefNum& left, const RefNum& right) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 553dbaae3..a9edd291e 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -33,6 +33,8 @@ namespace ESM class CellRef { + void clearData(); + public: // Reference number @@ -99,19 +101,15 @@ namespace ESM // Position and rotation of this object within the cell Position mPos; - bool mIsDeleted; - - CellRef(); - /// Calls loadId and loadData - void load (ESMReader& esm, bool wideRefNum = false); + void load (ESMReader& esm, bool &isDeleted, bool wideRefNum = false); void loadId (ESMReader& esm, bool wideRefNum = false); /// Implicitly called by load - void loadData (ESMReader& esm); + void loadData (ESMReader& esm, bool &isDeleted); - void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false) const; + void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index f927e98e8..56be91e71 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -6,27 +6,18 @@ unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; -ESM::DebugProfile::DebugProfile() - : mIsDeleted(false) -{} - -void ESM::DebugProfile::load (ESMReader& esm) +void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; @@ -36,6 +27,10 @@ void ESM::DebugProfile::load (ESMReader& esm) case ESM::FourCC<'F','L','A','G'>::value: esm.getHT(mFlags); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -43,11 +38,11 @@ void ESM::DebugProfile::load (ESMReader& esm) } } -void ESM::DebugProfile::save (ESMWriter& esm) const +void ESM::DebugProfile::save (ESMWriter& esm, bool isDeleted) const { esm.writeHNCString ("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -63,5 +58,4 @@ void ESM::DebugProfile::blank() mDescription.clear(); mScriptText.clear(); mFlags = 0; - mIsDeleted = false; } diff --git a/components/esm/debugprofile.hpp b/components/esm/debugprofile.hpp index 1709136f5..c056750a8 100644 --- a/components/esm/debugprofile.hpp +++ b/components/esm/debugprofile.hpp @@ -27,12 +27,8 @@ namespace ESM unsigned int mFlags; - bool mIsDeleted; - - DebugProfile(); - - void load (ESMReader& esm); - void save (ESMWriter& esm) const; + void load (ESMReader& esm, bool &isDeleted); + void save (ESMWriter& esm, bool isDeleted = false) const; /// Set record to default state (does not touch the ID). void blank(); diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 97fd8d986..400ead4e2 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -279,6 +279,7 @@ void ESMReader::skipRecord() { skip(mCtx.leftRec); mCtx.leftRec = 0; + mCtx.subCached = false; } void ESMReader::getRecHeader(uint32_t &flags) diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 39c05924b..5b33c6683 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -6,13 +6,9 @@ unsigned int ESM::Filter::sRecordId = REC_FILT; -ESM::Filter::Filter() - : mIsDeleted(false) -{} - -void ESM::Filter::load (ESMReader& esm) +void ESM::Filter::load (ESMReader& esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; while (esm.hasMoreSubs()) { @@ -23,16 +19,16 @@ void ESM::Filter::load (ESMReader& esm) case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'F','I','L','T'>::value: mFilter = esm.getHString(); break; case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -40,11 +36,11 @@ void ESM::Filter::load (ESMReader& esm) } } -void ESM::Filter::save (ESMWriter& esm) const +void ESM::Filter::save (ESMWriter& esm, bool isDeleted) const { esm.writeHNCString ("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -58,5 +54,4 @@ void ESM::Filter::blank() { mFilter.clear(); mDescription.clear(); - mIsDeleted = false; } diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp index 1a8af9229..b1c511ebb 100644 --- a/components/esm/filter.hpp +++ b/components/esm/filter.hpp @@ -18,12 +18,8 @@ namespace ESM std::string mFilter; - bool mIsDeleted; - - Filter(); - - void load (ESMReader& esm); - void save (ESMWriter& esm) const; + void load (ESMReader& esm, bool &isDeleted); + void save (ESMWriter& esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index c32cea1a6..14db45c34 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -8,29 +8,20 @@ namespace ESM { unsigned int Activator::sRecordId = REC_ACTI; - Activator::Activator() - : mIsDeleted(false) - {} - - void Activator::load(ESMReader &esm) + void Activator::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -40,6 +31,10 @@ namespace ESM case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -49,11 +44,11 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); } - void Activator::save(ESMWriter &esm) const + void Activator::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -69,6 +64,5 @@ namespace ESM mName.clear(); mScript.clear(); mModel.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp index 93de07b25..4cc72d528 100644 --- a/components/esm/loadacti.hpp +++ b/components/esm/loadacti.hpp @@ -17,12 +17,8 @@ struct Activator std::string mId, mName, mScript, mModel; - bool mIsDeleted; - - Activator(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index c1213583d..ceff4ba7d 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -8,31 +8,23 @@ namespace ESM { unsigned int Potion::sRecordId = REC_ALCH; - Potion::Potion() - : mIsDeleted(false) - {} - - void Potion::load(ESMReader &esm) + void Potion::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mEffects.mList.clear(); - mIsDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -52,6 +44,10 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEffects.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -60,14 +56,14 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing ALDT subrecord"); } - void Potion::save(ESMWriter &esm) const + void Potion::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -91,6 +87,5 @@ namespace ESM mIcon.clear(); mScript.clear(); mEffects.mList.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index 921585a9d..9ef390ebd 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -33,12 +33,8 @@ struct Potion std::string mId, mName, mModel, mIcon, mScript; EffectList mEffects; - bool mIsDeleted; - - Potion(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index edf1f473b..7a77ba421 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int Apparatus::sRecordId = REC_APPA; - Apparatus::Apparatus() - : mIsDeleted(false) - {} - - void Apparatus::load(ESMReader &esm) + void Apparatus::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -48,6 +39,10 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -56,15 +51,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing AADT subrecord"); } - void Apparatus::save(ESMWriter &esm) const + void Apparatus::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -87,6 +82,5 @@ namespace ESM mIcon.clear(); mScript.clear(); mName.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp index 2dac37995..0590d33ed 100644 --- a/components/esm/loadappa.hpp +++ b/components/esm/loadappa.hpp @@ -38,12 +38,8 @@ struct Apparatus AADTstruct mData; std::string mId, mModel, mIcon, mScript, mName; - bool mIsDeleted; - - Apparatus(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index d5b9fdd44..600c417be 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -38,31 +38,23 @@ namespace ESM unsigned int Armor::sRecordId = REC_ARMO; - Armor::Armor() - : mIsDeleted(false) - {} - - void Armor::load(ESMReader &esm) + void Armor::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mParts.mParts.clear(); - mIsDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -85,6 +77,10 @@ namespace ESM case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -93,15 +89,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing CTDT subrecord"); } - void Armor::save(ESMWriter &esm) const + void Armor::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -130,6 +126,5 @@ namespace ESM mIcon.clear(); mScript.clear(); mEnchant.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index 4ebe181a7..ef3bb734c 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -96,12 +96,8 @@ struct Armor std::string mId, mName, mModel, mIcon, mScript, mEnchant; - bool mIsDeleted; - - Armor(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index e2c6ad7b2..20d6b35cf 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int BodyPart::sRecordId = REC_BODY; - BodyPart::BodyPart() - : mIsDeleted(false) - {} - - void BodyPart::load(ESMReader &esm) + void BodyPart::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -42,6 +33,10 @@ namespace ESM esm.getHT(mData, 4); hasData = true; break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -50,15 +45,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing BYDT subrecord"); } - void BodyPart::save(ESMWriter &esm) const + void BodyPart::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -78,7 +73,5 @@ namespace ESM mModel.clear(); mRace.clear(); - - mIsDeleted = false; } } diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index f32d2fb58..bf320330f 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -60,12 +60,8 @@ struct BodyPart BYDTstruct mData; std::string mId, mModel, mRace; - bool mIsDeleted; - - BodyPart(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index 2d0d3ce75..b08b12f50 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int Book::sRecordId = REC_BOOK; - Book::Book() - : mIsDeleted(false) - {} - - void Book::load(ESMReader &esm) + void Book::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -54,6 +45,10 @@ namespace ESM case ESM::FourCC<'T','E','X','T'>::value: mText = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -62,14 +57,14 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing BKDT subrecord"); } - void Book::save(ESMWriter &esm) const + void Book::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -97,6 +92,5 @@ namespace ESM mScript.clear(); mEnchant.clear(); mText.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp index 931f813c0..3d50356ae 100644 --- a/components/esm/loadbook.hpp +++ b/components/esm/loadbook.hpp @@ -28,12 +28,8 @@ struct Book std::string mName, mModel, mIcon, mScript, mEnchant, mText; std::string mId; - bool mIsDeleted; - - Book(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 54de009aa..56dc1897c 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -8,30 +8,22 @@ namespace ESM { unsigned int BirthSign::sRecordId = REC_BSGN; - BirthSign::BirthSign() - : mIsDeleted(false) - {} - - void BirthSign::load(ESMReader &esm) + void BirthSign::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mPowers.mList.clear(); - mIsDeleted = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -44,6 +36,10 @@ namespace ESM case ESM::FourCC<'N','P','C','S'>::value: mPowers.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -54,9 +50,9 @@ namespace ESM esm.fail("Missing NAME subrecord"); } - void BirthSign::save(ESMWriter &esm) const + void BirthSign::save(ESMWriter &esm, bool isDeleted) const { - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); } @@ -75,7 +71,6 @@ namespace ESM mDescription.clear(); mTexture.clear(); mPowers.mList.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index 685ade82f..24d27a7f8 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -22,12 +22,8 @@ struct BirthSign // List of powers and abilities that come with this birth sign. SpellList mPowers; - bool mIsDeleted; - - BirthSign(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 7e02f0f85..2d8daa584 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -52,75 +52,98 @@ namespace ESM return ref.mRefNum == refNum; } - void Cell::load(ESMReader &esm, bool saveContext) + void Cell::load(ESMReader &esm, bool &isDeleted, bool saveContext) { - loadName(esm); - loadData(esm); + loadNameAndData(esm, isDeleted); loadCell(esm, saveContext); } - void Cell::loadName(ESMReader &esm) + void Cell::loadNameAndData(ESMReader &esm, bool &isDeleted) { - mName = esm.getHNString("NAME"); + isDeleted = false; - mIsDeleted = false; - if (esm.isNextSub("DELE")) + bool hasName = false; + bool hasData = false; + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) { - esm.skipHSub(); - mIsDeleted = true; + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mName = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData) + esm.fail("Missing DATA subrecord"); } void Cell::loadCell(ESMReader &esm, bool saveContext) { + mWater = 0.0f; + mWaterInt = false; + mMapColor = 0; + mRegion.clear(); mRefNumCounter = 0; - if (mData.mFlags & Interior) + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) { - // Interior cells - if (esm.isNextSub("INTV")) + esm.getSubName(); + switch (esm.retSubName().val) { - int waterl; - esm.getHT(waterl); - mWater = (float) waterl; - mWaterInt = true; + case ESM::FourCC<'I','N','T','V'>::value: + int waterl; + esm.getHT(waterl); + mWater = static_cast(waterl); + mWaterInt = true; + break; + case ESM::FourCC<'W','H','G','T'>::value: + esm.getHT(mWater); + break; + case ESM::FourCC<'A','M','B','I'>::value: + esm.getHT(mAmbi); + break; + case ESM::FourCC<'R','G','N','N'>::value: + mRegion = esm.getHString(); + break; + case ESM::FourCC<'N','A','M','5'>::value: + esm.getHT(mMapColor); + break; + case ESM::FourCC<'N','A','M','0'>::value: + esm.getHT(mRefNumCounter); + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; } - else if (esm.isNextSub("WHGT")) - { - esm.getHT(mWater); - } - - // Quasi-exterior cells have a region (which determines the - // weather), pure interior cells have ambient lighting - // instead. - if (mData.mFlags & QuasiEx) - mRegion = esm.getHNOString("RGNN"); - else if (esm.isNextSub("AMBI")) - esm.getHT(mAmbi); - } - else - { - // Exterior cells - mRegion = esm.getHNOString("RGNN"); - - mMapColor = 0; - esm.getHNOT(mMapColor, "NAM5"); - } - if (esm.isNextSub("NAM0")) { - esm.getHT(mRefNumCounter); } - if (saveContext) { + if (saveContext) + { mContextList.push_back(esm.getContext()); esm.skipRecord(); } } - void Cell::loadData(ESMReader &esm) - { - esm.getHNT(mData, "DATA", 12); - } - void Cell::postLoad(ESMReader &esm) { // Save position of the cell references and move on @@ -128,11 +151,11 @@ namespace ESM esm.skipRecord(); } - void Cell::save(ESMWriter &esm) const + void Cell::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mName); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); } @@ -183,8 +206,10 @@ namespace ESM } } - bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool ignoreMoves, MovedCellRef *mref) + bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool &isDeleted, bool ignoreMoves, MovedCellRef *mref) { + isDeleted = false; + // TODO: Try and document reference numbering, I don't think this has been done anywhere else. if (!esm.hasMoreSubs()) return false; @@ -207,12 +232,15 @@ namespace ESM } } - ref.load (esm); - - // Identify references belonging to a parent file and adapt the ID accordingly. - adjustRefNum (ref.mRefNum, esm); + if (esm.peekNextSub("FRMR")) + { + ref.load (esm, isDeleted); - return true; + // Identify references belonging to a parent file and adapt the ID accordingly. + adjustRefNum (ref.mRefNum, esm); + return true; + } + return false; } bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) @@ -242,8 +270,6 @@ namespace ESM mAmbi.mSunlight = 0; mAmbi.mFog = 0; mAmbi.mFogDensity = 0; - - mIsDeleted = false; } CellId Cell::getCellId() const diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index a1a758e3b..2a7a78a54 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -85,8 +85,7 @@ struct Cell mWater(0), mWaterInt(false), mMapColor(0), - mRefNumCounter(0), - mIsDeleted(false) + mRefNumCounter(0) {} // Interior cells are indexed by this (it's the 'id'), for exterior @@ -113,18 +112,15 @@ struct Cell CellRefTracker mLeasedRefs; MovedCellRefTracker mMovedRefs; - bool mIsDeleted; - void postLoad(ESMReader &esm); // This method is left in for compatibility with esmtool. Parsing moved references currently requires // passing ESMStore, bit it does not know about this parameter, so we do it this way. - void load(ESMReader &esm, bool saveContext = true); // Load everything (except references) - void loadName(ESMReader &esm); // Load NAME and checks for DELE - void loadData(ESMReader &esm); // Load DATAstruct only - void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except DATAstruct and references + void load(ESMReader &esm, bool &isDeleted, bool saveContext = true); // Load everything (except references) + void loadNameAndData(ESMReader &esm, bool &isDeleted); // Load NAME and DATAstruct + void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except NAME, DATAstruct and references - void save(ESMWriter &esm) const; + void save(ESMWriter &esm, bool isDeleted = false) const; bool isExterior() const { @@ -163,7 +159,11 @@ struct Cell reuse one memory location without blanking it between calls. */ /// \param ignoreMoves ignore MVRF record and read reference like a regular CellRef. - static bool getNextRef(ESMReader &esm, CellRef &ref, bool ignoreMoves = false, MovedCellRef *mref = 0); + static bool getNextRef(ESMReader &esm, + CellRef &ref, + bool &isDeleted, + bool ignoreMoves = false, + MovedCellRef *mref = 0); /* This fetches an MVRF record, which is used to track moved references. * Since they are comparably rare, we use a separate method for this. diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index b58c35d90..2ad14b9f2 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -22,10 +22,6 @@ namespace ESM "sSpecializationStealth" }; - Class::Class() - : mIsDeleted(false) - {} - int& Class::CLDTstruct::getSkill (int index, bool major) { if (index<0 || index>=5) @@ -42,26 +38,21 @@ namespace ESM return mSkills[index][major ? 1 : 0]; } - void Class::load(ESMReader &esm) + void Class::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -74,6 +65,10 @@ namespace ESM case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -82,14 +77,14 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing CLDT subrecord"); } - void Class::save(ESMWriter &esm) const + void Class::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -113,7 +108,5 @@ namespace ESM for (int i=0; i<5; ++i) for (int i2=0; i2<2; ++i2) mData.mSkills[i][i2] = 0; - - mIsDeleted = false; } } diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index 399e93c24..833dd6757 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -73,12 +73,8 @@ struct Class std::string mId, mName, mDescription; CLDTstruct mData; - bool mIsDeleted; - - Class(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 18f7cd44f..8a88e6d7a 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -8,31 +8,23 @@ namespace ESM { unsigned int Clothing::sRecordId = REC_CLOT; - Clothing::Clothing() - : mIsDeleted(false) - {} - - void Clothing::load(ESMReader &esm) + void Clothing::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mParts.mParts.clear(); - mIsDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -55,6 +47,10 @@ namespace ESM case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -63,15 +59,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing CTDT subrecord"); } - void Clothing::save(ESMWriter &esm) const + void Clothing::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -101,6 +97,5 @@ namespace ESM mIcon.clear(); mEnchant.clear(); mScript.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp index 202c1ec45..39e5ea672 100644 --- a/components/esm/loadclot.hpp +++ b/components/esm/loadclot.hpp @@ -48,12 +48,8 @@ struct Clothing std::string mId, mName, mModel, mIcon, mEnchant, mScript; - bool mIsDeleted; - - Clothing(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index fadfe5f0f..372683750 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -24,16 +24,11 @@ namespace ESM unsigned int Container::sRecordId = REC_CONT; - Container::Container() - : mWeight(0), - mFlags(0x8), - mIsDeleted(false) - {} - - void Container::load(ESMReader &esm) + void Container::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mInventory.mList.clear(); - mIsDeleted = false; bool hasName = false; bool hasWeight = false; @@ -41,17 +36,12 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -76,6 +66,10 @@ namespace ESM case ESM::FourCC<'N','P','C','O'>::value: mInventory.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -84,17 +78,17 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasWeight && !mIsDeleted) + if (!hasWeight && !isDeleted) esm.fail("Missing CNDT subrecord"); - if (!hasFlags && !mIsDeleted) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void Container::save(ESMWriter &esm) const + void Container::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -118,6 +112,5 @@ namespace ESM mWeight = 0; mFlags = 0x8; // set default flag value mInventory.mList.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index 31c7e1815..4c847f4e2 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -52,12 +52,8 @@ struct Container int mFlags; InventoryList mInventory; - bool mIsDeleted; - - Container(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index b58e66239..e593ff540 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -8,14 +8,10 @@ namespace ESM { unsigned int Creature::sRecordId = REC_CREA; - Creature::Creature() - : mFlags(0), - mScale(0.0f), - mIsDeleted(false) - {} - - void Creature::load(ESMReader &esm) + void Creature::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mPersistent = (esm.getRecordFlags() & 0x0400) != 0; mAiPackage.mList.clear(); @@ -25,7 +21,6 @@ namespace ESM { mScale = 1.f; mHasAI = false; - mIsDeleted = false; bool hasName = false; bool hasNpdt = false; @@ -33,17 +28,12 @@ namespace ESM { while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -89,6 +79,10 @@ namespace ESM { case AI_CNDT: mAiPackage.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -97,17 +91,17 @@ namespace ESM { if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasNpdt && !mIsDeleted) + if (!hasNpdt && !isDeleted) esm.fail("Missing NPDT subrecord"); - if (!hasFlags && !mIsDeleted) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void Creature::save(ESMWriter &esm) const + void Creature::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -161,7 +155,6 @@ namespace ESM { mAiData.mServices = 0; mAiPackage.mList.clear(); mTransport.mList.clear(); - mIsDeleted = false; } const std::vector& Creature::getTransport() const diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 22e834e45..a5147619c 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -96,14 +96,10 @@ struct Creature AIPackageList mAiPackage; Transport mTransport; - bool mIsDeleted; - - Creature(); - const std::vector& getTransport() const; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index 74a708805..30fa3cfef 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -10,29 +10,25 @@ namespace ESM { unsigned int Dialogue::sRecordId = REC_DIAL; - Dialogue::Dialogue() - : mIsDeleted(false) - {} - - void Dialogue::load(ESMReader &esm) + void Dialogue::load(ESMReader &esm, bool &isDeleted) { loadId(esm); - loadData(esm); + loadData(esm, isDeleted); } void Dialogue::loadId(ESMReader &esm) { - mIsDeleted = false; mId = esm.getHNString("NAME"); } - void Dialogue::loadData(ESMReader &esm) + void Dialogue::loadData(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'D','A','T','A'>::value: { @@ -51,7 +47,7 @@ namespace ESM case ESM::FourCC<'D','E','L','E'>::value: esm.skipHSub(); mType = Unknown; - mIsDeleted = true; + isDeleted = true; break; default: esm.fail("Unknown subrecord"); @@ -60,10 +56,10 @@ namespace ESM } } - void Dialogue::save(ESMWriter &esm) const + void Dialogue::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); } @@ -76,7 +72,6 @@ namespace ESM void Dialogue::blank() { mInfo.clear(); - mIsDeleted = false; } void Dialogue::readInfo(ESMReader &esm, bool merge) @@ -84,25 +79,27 @@ namespace ESM ESM::DialInfo info; info.loadId(esm); + bool isDeleted = false; if (!merge || mInfo.empty()) { - info.loadInfo(esm); - mLookup[info.mId] = mInfo.insert(mInfo.end(), info); + info.loadData(esm, isDeleted); + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); + return; } - ESM::Dialogue::InfoContainer::iterator it = mInfo.end(); + InfoContainer::iterator it = mInfo.end(); - std::map::iterator lookup; + LookupMap::iterator lookup; lookup = mLookup.find(info.mId); if (lookup != mLookup.end()) { - it = lookup->second; + it = lookup->second.first; // Merge with existing record. Only the subrecords that are present in // the new record will be overwritten. - it->loadInfo(esm); + it->loadData(esm, isDeleted); info = *it; // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record @@ -111,35 +108,35 @@ namespace ESM } else { - info.loadInfo(esm); + info.loadData(esm, isDeleted); } if (info.mNext.empty()) { - mLookup[info.mId] = mInfo.insert(mInfo.end(), info); + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); return; } if (info.mPrev.empty()) { - mLookup[info.mId] = mInfo.insert(mInfo.begin(), info); + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.begin(), info), isDeleted); return; } lookup = mLookup.find(info.mPrev); if (lookup != mLookup.end()) { - it = lookup->second; + it = lookup->second.first; - mLookup[info.mId] = mInfo.insert(++it, info); + mLookup[info.mId] = std::make_pair(mInfo.insert(++it, info), isDeleted); return; } lookup = mLookup.find(info.mNext); if (lookup != mLookup.end()) { - it = lookup->second; + it = lookup->second.first; - mLookup[info.mId] = mInfo.insert(it, info); + mLookup[info.mId] = std::make_pair(mInfo.insert(it, info), isDeleted); return; } @@ -148,12 +145,15 @@ namespace ESM void Dialogue::clearDeletedInfos() { - for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); ) + LookupMap::const_iterator current = mLookup.begin(); + LookupMap::const_iterator end = mLookup.end(); + for (; current != end; ++current) { - if (it->mIsDeleted) - it = mInfo.erase(it); - else - ++it; + if (current->second.second) + { + mInfo.erase(current->second.first); + } } + mLookup.clear(); } } diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 73bf16974..e517fc86f 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "loadinfo.hpp" @@ -39,27 +40,24 @@ struct Dialogue typedef std::list InfoContainer; - typedef std::map LookupMap; + // Parameters: Info ID, (Info iterator, Deleted flag) + typedef std::map > LookupMap; InfoContainer mInfo; // This is only used during the loading phase to speed up DialInfo merging. LookupMap mLookup; - bool mIsDeleted; - - Dialogue(); - - void load(ESMReader &esm); + void load(ESMReader &esm, bool &isDeleted); ///< Loads all sub-records of Dialogue record void loadId(ESMReader &esm); ///< Loads NAME sub-record of Dialogue record - void loadData(ESMReader &esm); + void loadData(ESMReader &esm, bool &isDeleted); ///< Loads all sub-records of Dialogue record, except NAME sub-record - void save(ESMWriter &esm) const; + void save(ESMWriter &esm, bool isDeleted = false) const; - /// Remove all INFOs that are deleted or marked as QS_Deleted from mInfos. + /// Remove all INFOs that are deleted void clearDeletedInfos(); /// Read the next info record diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index 4f58a4261..706e938e8 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -8,29 +8,20 @@ namespace ESM { unsigned int Door::sRecordId = REC_DOOR; - Door::Door() - : mIsDeleted(false) - {} - - void Door::load(ESMReader &esm) + void Door::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -46,6 +37,10 @@ namespace ESM case ESM::FourCC<'A','N','A','M'>::value: mCloseSound = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -56,11 +51,11 @@ namespace ESM esm.fail("Missing NAME subrecord"); } - void Door::save(ESMWriter &esm) const + void Door::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -80,6 +75,5 @@ namespace ESM mScript.clear(); mOpenSound.clear(); mCloseSound.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index 546471ed3..3afe5d5e4 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -17,12 +17,8 @@ struct Door std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound; - bool mIsDeleted; - - Door(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 0e480c379..5580ef222 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -8,31 +8,22 @@ namespace ESM { unsigned int Enchantment::sRecordId = REC_ENCH; - Enchantment::Enchantment() - : mIsDeleted(false) - {} - - void Enchantment::load(ESMReader &esm) + void Enchantment::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; mEffects.mList.clear(); - mIsDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'E','N','D','T'>::value: esm.getHT(mData, 16); hasData = true; @@ -40,6 +31,10 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEffects.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -48,15 +43,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing ENDT subrecord"); } - void Enchantment::save(ESMWriter &esm) const + void Enchantment::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -74,7 +69,5 @@ namespace ESM mData.mAutocalc = 0; mEffects.mList.clear(); - - mIsDeleted = false; } } diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index 6ebe8e940..7b93b519c 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -42,12 +42,8 @@ struct Enchantment ENDTstruct mData; EffectList mEffects; - bool mIsDeleted; - - Enchantment(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 8538b0b95..f550a5538 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -10,10 +10,6 @@ namespace ESM { unsigned int Faction::sRecordId = REC_FACT; - Faction::Faction() - : mIsDeleted(false) - {} - int& Faction::FADTstruct::getSkill (int index, bool ignored) { if (index<0 || index>=7) @@ -30,9 +26,10 @@ namespace ESM return mSkills[index]; } - void Faction::load(ESMReader &esm) + void Faction::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; + mReactions.clear(); for (int i=0;i<10;++i) mRanks[i].clear(); @@ -43,17 +40,12 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -76,6 +68,10 @@ namespace ESM mReactions[faction] = reaction; break; } + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -84,15 +80,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing FADT subrecord"); } - void Faction::save(ESMWriter &esm) const + void Faction::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -136,7 +132,5 @@ namespace ESM mData.mSkills[i] = 0; mReactions.clear(); - - mIsDeleted = false; } } diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 96c02028b..cc715d266 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -62,12 +62,8 @@ struct Faction // Name of faction ranks (may be empty for NPC factions) std::string mRanks[10]; - bool mIsDeleted; - - Faction(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index 5f96aff1f..72ecce503 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -8,19 +8,16 @@ namespace ESM { unsigned int Global::sRecordId = REC_GLOB; - Global::Global() - : mIsDeleted(false) - {} - - void Global::load (ESMReader &esm) + void Global::load (ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; + mId = esm.getHNString ("NAME"); if (esm.isNextSub ("DELE")) { esm.skipHSub(); - mIsDeleted = true; + isDeleted = true; } else { @@ -28,11 +25,11 @@ namespace ESM } } - void Global::save (ESMWriter &esm) const + void Global::save (ESMWriter &esm, bool isDeleted) const { esm.writeHNCString ("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString ("DELE", ""); } @@ -45,7 +42,6 @@ namespace ESM void Global::blank() { mValue.setType (ESM::VT_None); - mIsDeleted = false; } bool operator== (const Global& left, const Global& right) diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index c0219c0ba..0533cc95e 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -24,12 +24,8 @@ struct Global std::string mId; Variant mValue; - bool mIsDeleted; - - Global(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index 9e2a80270..1ebb002e6 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -8,13 +8,15 @@ namespace ESM { unsigned int GameSetting::sRecordId = REC_GMST; - void GameSetting::load (ESMReader &esm) + void GameSetting::load (ESMReader &esm, bool &isDeleted) { + isDeleted = false; // GameSetting record can't be deleted now (may be changed in the future) + mId = esm.getHNString("NAME"); mValue.read (esm, ESM::Variant::Format_Gmst); } - void GameSetting::save (ESMWriter &esm) const + void GameSetting::save (ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNCString("NAME", mId); mValue.write (esm, ESM::Variant::Format_Gmst); diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index d9d9048b6..73a723e81 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -26,7 +26,7 @@ struct GameSetting Variant mValue; - void load(ESMReader &esm); + void load(ESMReader &esm, bool &isDeleted); /// \todo remove the get* functions (redundant, since mValue has equivalent functions now). @@ -39,7 +39,7 @@ struct GameSetting std::string getString() const; ///< Throwns an exception if GMST is not of type string. - void save(ESMWriter &esm) const; + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 89fd4e0cd..d1f20a3d4 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -8,29 +8,23 @@ namespace ESM { unsigned int DialInfo::sRecordId = REC_INFO; - DialInfo::DialInfo() - : mFactionLess(false), - mQuestStatus(QS_None), - mIsDeleted(false) - {} - - void DialInfo::load(ESMReader &esm) + void DialInfo::load(ESMReader &esm, bool &isDeleted) { loadId(esm); - loadInfo(esm); + loadData(esm, isDeleted); } void DialInfo::loadId(ESMReader &esm) { - mIsDeleted = false; mId = esm.getHNString("INAM"); } - void DialInfo::loadInfo(ESMReader &esm) + void DialInfo::loadData(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mQuestStatus = QS_None; mFactionLess = false; - mIsDeleted = false; mPrev = esm.getHNString("PNAM"); mNext = esm.getHNString("NNAM"); @@ -41,13 +35,8 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mData, 12); break; @@ -104,6 +93,10 @@ namespace ESM mQuestStatus = QS_Restart; esm.skipRecord(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -111,13 +104,13 @@ namespace ESM } } - void DialInfo::save(ESMWriter &esm) const + void DialInfo::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("INAM", mId); esm.writeHNCString("PNAM", mPrev); esm.writeHNCString("NNAM", mNext); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -173,6 +166,5 @@ namespace ESM mResultScript.clear(); mFactionLess = false; mQuestStatus = QS_None; - mIsDeleted = false; } } diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 65363d1be..8123a9ace 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -105,18 +105,14 @@ struct DialInfo REC_DELE = 0x454c4544 }; - bool mIsDeleted; - - DialInfo(); - - void load(ESMReader &esm); + void load(ESMReader &esm, bool &isDeleted); ///< Loads all sub-records of Info record void loadId(ESMReader &esm); ///< Loads only Id of Info record (INAM sub-record) - void loadInfo(ESMReader &esm); + void loadData(ESMReader &esm, bool &isDeleted); ///< Loads all sub-records of Info record, except INAM sub-record - void save(ESMWriter &esm) const; + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 51a1f4805..a481d5b79 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int Ingredient::sRecordId = REC_INGR; - Ingredient::Ingredient() - : mIsDeleted(false) - {} - - void Ingredient::load(ESMReader &esm) + void Ingredient::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -48,6 +39,10 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -56,7 +51,7 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing IRDT subrecord"); // horrible hack to fix broken data in records @@ -83,11 +78,11 @@ namespace ESM } } - void Ingredient::save(ESMWriter &esm) const + void Ingredient::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -115,7 +110,5 @@ namespace ESM mModel.clear(); mIcon.clear(); mScript.clear(); - - mIsDeleted = false; } } diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index c92f28f18..c0f445023 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -31,12 +31,8 @@ struct Ingredient IRDTstruct mData; std::string mId, mName, mModel, mIcon, mScript; - bool mIsDeleted; - - Ingredient(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 6033f83cc..65e30f00f 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -74,7 +74,6 @@ namespace ESM , mDataTypes(0) , mDataLoaded(false) , mLandData(NULL) - , mIsDeleted(false) { } @@ -83,61 +82,82 @@ namespace ESM delete mLandData; } - void Land::load(ESMReader &esm) + void Land::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mEsm = &esm; mPlugin = mEsm->getIndex(); - mIsDeleted = false; - - // Get the grid location - esm.getSubNameIs("INTV"); - esm.getSubHeaderIs(8); - esm.getT(mX); - esm.getT(mY); - - esm.getHNT(mFlags, "DATA"); - if (esm.isNextSub("DELE")) + bool hasLocation = false; + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) { - esm.skipHSub(); - mIsDeleted = true; + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'I','N','T','V'>::value: + esm.getSubHeaderIs(8); + esm.getT(mX); + esm.getT(mY); + hasLocation = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mFlags); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } } - // Store the file position + if (!hasLocation) + esm.fail("Missing INTV subrecord"); + mContext = esm.getContext(); - // Skip these here. Load the actual data when the cell is loaded. - if (esm.isNextSub("VNML")) - { - esm.skipHSubSize(12675); - mDataTypes |= DATA_VNML; - } - if (esm.isNextSub("VHGT")) - { - esm.skipHSubSize(4232); - mDataTypes |= DATA_VHGT; - } - if (esm.isNextSub("WNAM")) + // Skip the land data here. Load it when the cell is loaded. + while (esm.hasMoreSubs()) { - esm.skipHSubSize(81); - mDataTypes |= DATA_WNAM; - } - if (esm.isNextSub("VCLR")) - { - esm.skipHSubSize(12675); - mDataTypes |= DATA_VCLR; - } - if (esm.isNextSub("VTEX")) - { - esm.skipHSubSize(512); - mDataTypes |= DATA_VTEX; + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'V','N','M','L'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VNML; + break; + case ESM::FourCC<'V','H','G','T'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VHGT; + break; + case ESM::FourCC<'W','N','A','M'>::value: + esm.skipHSub(); + mDataTypes |= DATA_WNAM; + break; + case ESM::FourCC<'V','C','L','R'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VCLR; + break; + case ESM::FourCC<'V','T','E','X'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VTEX; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } mDataLoaded = 0; mLandData = NULL; } - void Land::save(ESMWriter &esm) const + void Land::save(ESMWriter &esm, bool isDeleted) const { esm.startSubRecord("INTV"); esm.writeT(mX); @@ -146,22 +166,17 @@ namespace ESM esm.writeHNT("DATA", mFlags); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); } - if (mLandData != NULL) + if (mLandData) { mLandData->save(esm); } } - void Land::blank() - { - mIsDeleted = false; - } - void Land::loadData(int flags) const { // Try to load only available data diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 297d54883..65ac88cda 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -106,12 +106,10 @@ struct Land static void transposeTextureData(const uint16_t *in, uint16_t *out); }; - bool mIsDeleted; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; - - void blank(); + void blank() {} /** * Actually loads data diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index 9c34ef657..6245ec856 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -6,29 +6,21 @@ namespace ESM { - LevelledListBase::LevelledListBase() - : mIsDeleted(false) - {} - - void LevelledListBase::load(ESMReader &esm) + void LevelledListBase::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; + bool hasList = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mFlags); break; @@ -53,12 +45,28 @@ namespace ESM li.mId = esm.getHNString(mRecName); esm.getHNT(li.mLevel, "INTV"); } + + hasList = true; break; } + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: - mList.clear(); - esm.skipRecord(); + { + if (!hasList) + { + // Original engine ignores rest of the record, even if there are items following + mList.clear(); + esm.skipRecord(); + } + else + { + esm.fail("Unknown subrecord"); + } break; + } } } @@ -66,11 +74,11 @@ namespace ESM esm.fail("Missing NAME subrecord"); } - void LevelledListBase::save(ESMWriter &esm) const + void LevelledListBase::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -92,7 +100,6 @@ namespace ESM mFlags = 0; mChanceNone = 0; mList.clear(); - mIsDeleted = false; } unsigned int CreatureLevList::sRecordId = REC_LEVC; diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index 14ebc9937..ed4131c16 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -36,12 +36,8 @@ struct LevelledListBase std::vector mList; - bool mIsDeleted; - - LevelledListBase(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index 441e96d0a..a0fedc3ad 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int Light::sRecordId = REC_LIGH; - Light::Light() - : mIsDeleted(false) - {} - - void Light::load(ESMReader &esm) + void Light::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -51,6 +42,10 @@ namespace ESM case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -59,14 +54,14 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing LHDT subrecord"); } - void Light::save(ESMWriter &esm) const + void Light::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -93,6 +88,5 @@ namespace ESM mModel.clear(); mIcon.clear(); mName.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index d4d3418d8..8509c64b6 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -47,12 +47,8 @@ struct Light std::string mSound, mScript, mModel, mIcon, mName, mId; - bool mIsDeleted; - - Light(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 2cfe43743..be93eaa0e 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int Lockpick::sRecordId = REC_LOCK; - Lockpick::Lockpick() - : mIsDeleted(false) - {} - - void Lockpick::load(ESMReader &esm) + void Lockpick::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -48,6 +39,10 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -56,15 +51,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing LKDT subrecord"); } - void Lockpick::save(ESMWriter &esm) const + void Lockpick::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -88,6 +83,5 @@ namespace ESM mModel.clear(); mIcon.clear(); mScript.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp index ce7de2c06..9db41af97 100644 --- a/components/esm/loadlock.hpp +++ b/components/esm/loadlock.hpp @@ -27,12 +27,8 @@ struct Lockpick Data mData; std::string mId, mName, mModel, mIcon, mScript; - bool mIsDeleted; - - Lockpick(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index 6bd48d801..cf026dbf1 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int LandTexture::sRecordId = REC_LTEX; - LandTexture::LandTexture() - : mIsDeleted(false) - {} - - void LandTexture::load(ESMReader &esm) + void LandTexture::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasIndex = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'I','N','T','V'>::value: esm.getHT(mIndex); hasIndex = true; @@ -39,6 +30,10 @@ namespace ESM case ESM::FourCC<'D','A','T','A'>::value: mTexture = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -50,9 +45,9 @@ namespace ESM if (!hasIndex) esm.fail("Missing INTV subrecord"); } - void LandTexture::save(ESMWriter &esm) const + void LandTexture::save(ESMWriter &esm, bool isDeleted) const { - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); } @@ -66,6 +61,5 @@ namespace ESM { mTexture.clear(); mIndex = -1; - mIsDeleted = false; } } diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index 33af77612..2cb5abf0c 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -34,12 +34,8 @@ struct LandTexture std::string mId, mTexture; int mIndex; - bool mIsDeleted; - - LandTexture(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 6f859ab3c..eef58aa2f 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -189,8 +189,10 @@ namespace ESM { unsigned int MagicEffect::sRecordId = REC_MGEF; -void MagicEffect::load(ESMReader &esm) +void MagicEffect::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; // MagicEffect record can't be deleted now (may be changed in the future) + esm.getHNT(mIndex, "INDX"); mId = indexToId (mIndex); @@ -209,8 +211,7 @@ void MagicEffect::load(ESMReader &esm) while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); @@ -250,7 +251,7 @@ void MagicEffect::load(ESMReader &esm) } } } -void MagicEffect::save(ESMWriter &esm) const +void MagicEffect::save(ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", mIndex); diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index eeb4268c2..a52ed797f 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -96,8 +96,8 @@ struct MagicEffect // sMagicCreature04ID/05ID. int mIndex; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; /// Set record to default state (does not touch the ID/index). void blank(); diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index de9ccdd6a..f03cb9a85 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int Miscellaneous::sRecordId = REC_MISC; - Miscellaneous::Miscellaneous() - : mIsDeleted(false) - {} - - void Miscellaneous::load(ESMReader &esm) + void Miscellaneous::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -48,6 +39,10 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -56,15 +51,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing MCDT subrecord"); } - void Miscellaneous::save(ESMWriter &esm) const + void Miscellaneous::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -86,6 +81,5 @@ namespace ESM mModel.clear(); mIcon.clear(); mScript.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadmisc.hpp b/components/esm/loadmisc.hpp index 82018cd72..e7a323904 100644 --- a/components/esm/loadmisc.hpp +++ b/components/esm/loadmisc.hpp @@ -32,12 +32,8 @@ struct Miscellaneous std::string mId, mName, mModel, mIcon, mScript; - bool mIsDeleted; - - Miscellaneous(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index ffbbb59a0..72333add3 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -8,15 +8,10 @@ namespace ESM { unsigned int NPC::sRecordId = REC_NPC_; - NPC::NPC() - : mFlags(0), - mHasAI(false), - mIsDeleted(false) - {} - - void NPC::load(ESMReader &esm) + void NPC::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; + mPersistent = (esm.getRecordFlags() & 0x0400) != 0; mSpells.mList.clear(); @@ -31,17 +26,12 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -108,6 +98,10 @@ namespace ESM case AI_CNDT: mAiPackage.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -116,16 +110,16 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasNpdt && !mIsDeleted) + if (!hasNpdt && !isDeleted) esm.fail("Missing NPDT subrecord"); - if (!hasFlags && !mIsDeleted) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void NPC::save(ESMWriter &esm) const + void NPC::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -211,7 +205,6 @@ namespace ESM mScript.clear(); mHair.clear(); mHead.clear(); - mIsDeleted = false; } int NPC::getFactionRank() const diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 263752752..5b89f4055 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -130,12 +130,8 @@ struct NPC // body parts std::string mHair, mHead; - bool mIsDeleted; - - NPC(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; bool isMale() const; diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 5e8de9d57..8027be91c 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -32,12 +32,10 @@ namespace ESM { } - Pathgrid::Pathgrid() - : mIsDeleted(false) - {} - - void Pathgrid::load(ESMReader &esm) + void Pathgrid::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mPoints.clear(); mEdges.clear(); @@ -49,8 +47,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mData, 12); @@ -118,7 +115,7 @@ namespace ESM } case ESM::FourCC<'D','E','L','E'>::value: esm.skipHSub(); - mIsDeleted = true; + isDeleted = true; break; default: esm.fail("Unknown subrecord"); @@ -132,12 +129,12 @@ namespace ESM esm.fail("Missing NAME subrecord"); } - void Pathgrid::save(ESMWriter &esm) const + void Pathgrid::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNT("DATA", mData, 12); esm.writeHNCString("NAME", mCell); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -173,6 +170,5 @@ namespace ESM mData.mS2 = 0; mPoints.clear(); mEdges.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index 4b82d9571..d1003eb86 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -53,12 +53,8 @@ struct Pathgrid typedef std::vector EdgeList; EdgeList mEdges; - bool mIsDeleted; - - Pathgrid(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index 4ce9b9d9c..31caeff41 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int Probe::sRecordId = REC_PROB; - Probe::Probe() - : mIsDeleted(false) - {} - - void Probe::load(ESMReader &esm) + void Probe::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -48,6 +39,10 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -56,15 +51,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing PBDT subrecord"); } - void Probe::save(ESMWriter &esm) const + void Probe::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -88,6 +83,5 @@ namespace ESM mModel.clear(); mIcon.clear(); mScript.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp index 748d498fc..da203b456 100644 --- a/components/esm/loadprob.hpp +++ b/components/esm/loadprob.hpp @@ -27,12 +27,8 @@ struct Probe Data mData; std::string mId, mName, mModel, mIcon, mScript; - bool mIsDeleted; - - Probe(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index 12762bda3..d5172a133 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -8,10 +8,6 @@ namespace ESM { unsigned int Race::sRecordId = REC_RACE; - Race::Race() - : mIsDeleted(false) - {} - int Race::MaleFemale::getValue (bool male) const { return male ? mMale : mFemale; @@ -22,27 +18,23 @@ namespace ESM return static_cast(male ? mMale : mFemale); } - void Race::load(ESMReader &esm) + void Race::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mPowers.mList.clear(); - mIsDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -56,6 +48,10 @@ namespace ESM case ESM::FourCC<'N','P','C','S'>::value: mPowers.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); } @@ -63,14 +59,14 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing RADT subrecord"); } - void Race::save(ESMWriter &esm) const + void Race::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index e8e9a442b..bf0573075 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -68,12 +68,8 @@ struct Race std::string mId, mName, mDescription; SpellList mPowers; - bool mIsDeleted; - - Race(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index b48ffa4b7..b04e6ee3b 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -8,29 +8,20 @@ namespace ESM { unsigned int Region::sRecordId = REC_REGN; - Region::Region() - : mMapColor(0), - mIsDeleted(false) - {} - - void Region::load(ESMReader &esm) + void Region::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); @@ -75,6 +66,9 @@ namespace ESM esm.getHT(sr, 33); mSoundList.push_back(sr); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; default: esm.fail("Unknown subrecord"); break; @@ -85,9 +79,9 @@ namespace ESM esm.fail("Missing NAME subrecord"); } - void Region::save(ESMWriter &esm) const + void Region::save(ESMWriter &esm, bool isDeleted) const { - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); } @@ -121,7 +115,5 @@ namespace ESM mName.clear(); mSleepList.clear(); mSoundList.clear(); - - mIsDeleted = false; } } diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index 9082437fe..3d914bd17 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -51,12 +51,8 @@ struct Region std::vector mSoundList; - bool mIsDeleted; - - Region(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index 74e682d63..00d2ebf08 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int Repair::sRecordId = REC_REPA; - Repair::Repair() - : mIsDeleted(false) - {} - - void Repair::load(ESMReader &esm) + void Repair::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -48,6 +39,10 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -56,15 +51,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing RIDT subrecord"); } - void Repair::save(ESMWriter &esm) const + void Repair::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -88,6 +83,5 @@ namespace ESM mModel.clear(); mIcon.clear(); mScript.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp index 1448f9c77..2537c53cb 100644 --- a/components/esm/loadrepa.hpp +++ b/components/esm/loadrepa.hpp @@ -27,12 +27,8 @@ struct Repair Data mData; std::string mId, mName, mModel, mIcon, mScript; - bool mIsDeleted; - - Repair(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 66d9d0057..09bf7f125 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -8,10 +8,6 @@ namespace ESM { unsigned int Script::sRecordId = REC_SCPT; - Script::Script() - : mIsDeleted(false) - {} - void Script::loadSCVR(ESMReader &esm) { int s = mData.mStringTableSize; @@ -59,17 +55,17 @@ namespace ESM } } - void Script::load(ESMReader &esm) + void Script::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mVarNames.clear(); - mIsDeleted = false; bool hasHeader = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'S','C','H','D'>::value: SCHD data; @@ -78,10 +74,6 @@ namespace ESM mId = data.mName.toString(); hasHeader = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'S','C','V','R'>::value: // list of local variables loadSCVR(esm); @@ -99,6 +91,10 @@ namespace ESM case ESM::FourCC<'S','C','T','X'>::value: mScriptText = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -109,7 +105,7 @@ namespace ESM esm.fail("Missing SCHD subrecord"); } - void Script::save(ESMWriter &esm) const + void Script::save(ESMWriter &esm, bool isDeleted) const { std::string varNameString; if (!mVarNames.empty()) @@ -124,7 +120,7 @@ namespace ESM esm.writeHNT("SCHD", data, 52); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); } @@ -159,8 +155,6 @@ namespace ESM mScriptText = "Begin \"" + mId + "\"\n\nEnd " + mId + "\n"; else mScriptText = "Begin " + mId + "\n\nEnd " + mId + "\n"; - - mIsDeleted = false; } } diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index 58b5842e8..b8a06406d 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -50,12 +50,8 @@ public: /// Script source code std::string mScriptText; - bool mIsDeleted; - - Script(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index 7883b8a1a..c52089791 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -129,15 +129,16 @@ namespace ESM unsigned int Skill::sRecordId = REC_SKIL; - void Skill::load(ESMReader &esm) + void Skill::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; // Skill record can't be deleted now (may be changed in the future) + bool hasIndex = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'I','N','D','X'>::value: esm.getHT(mIndex); @@ -164,7 +165,7 @@ namespace ESM mId = indexToId (mIndex); } - void Skill::save(ESMWriter &esm) const + void Skill::save(ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", mIndex); esm.writeHNT("SKDT", mData, 24); diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index e00184297..5430b422d 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -78,8 +78,8 @@ struct Skill static const std::string sIconNames[Length]; static const boost::array sSkillIds; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index a20e6ee51..400b1072b 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -8,31 +8,21 @@ namespace ESM { unsigned int SoundGenerator::sRecordId = REC_SNDG; - SoundGenerator::SoundGenerator() - : mType(LeftFoot), - mIsDeleted(false) - {} - - void SoundGenerator::load(ESMReader &esm) + void SoundGenerator::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mType, 4); hasData = true; @@ -43,6 +33,10 @@ namespace ESM case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -51,17 +45,17 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing DATA subrecord"); } - void SoundGenerator::save(ESMWriter &esm) const + void SoundGenerator::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); esm.writeHNT("DATA", mType, 4); esm.writeHNOCString("CNAM", mCreature); esm.writeHNOCString("SNAM", mSound); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); } @@ -72,6 +66,5 @@ namespace ESM mType = LeftFoot; mCreature.clear(); mSound.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index 13eb18072..70b221e98 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -36,12 +36,8 @@ struct SoundGenerator std::string mId, mCreature, mSound; - bool mIsDeleted; - - SoundGenerator(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 55fe69292..d3a82e198 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int Sound::sRecordId = REC_SOUN; - Sound::Sound() - : mIsDeleted(false) - {} - - void Sound::load(ESMReader &esm) + void Sound::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'F','N','A','M'>::value: mSound = esm.getHString(); break; @@ -39,6 +30,10 @@ namespace ESM esm.getHT(mData, 3); hasData = true; break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -47,15 +42,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing DATA subrecord"); } - void Sound::save(ESMWriter &esm) const + void Sound::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -72,7 +67,5 @@ namespace ESM mData.mVolume = 128; mData.mMinRange = 0; mData.mMaxRange = 255; - - mIsDeleted = false; } } diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index 0b40ae751..937e22be8 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -23,12 +23,8 @@ struct Sound SOUNstruct mData; std::string mId, mSound; - bool mIsDeleted; - - Sound(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 28feffd20..16ffb63ff 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -8,32 +8,23 @@ namespace ESM { unsigned int Spell::sRecordId = REC_SPEL; - Spell::Spell() - : mIsDeleted(false) - {} - - void Spell::load(ESMReader &esm) + void Spell::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mEffects.mList.clear(); - mIsDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t val = esm.retSubName().val; - - switch (val) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -46,6 +37,10 @@ namespace ESM esm.getHT(s, 24); mEffects.mList.push_back(s); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -54,15 +49,15 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing SPDT subrecord"); } - void Spell::save(ESMWriter &esm) const + void Spell::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -81,6 +76,5 @@ namespace ESM mName.clear(); mEffects.mList.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index 327e94d8f..1763d0991 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -45,12 +45,8 @@ struct Spell std::string mId, mName; EffectList mEffects; - bool mIsDeleted; - - Spell(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index 076f73742..6af6c96dc 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -8,21 +8,16 @@ namespace ESM { unsigned int StartScript::sRecordId = REC_SSCR; - StartScript::StartScript() - : mIsDeleted(false) - {} - - void StartScript::load(ESMReader &esm) + void StartScript::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasData = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'D','A','T','A'>::value: mData = esm.getHString(); @@ -34,7 +29,7 @@ namespace ESM break; case ESM::FourCC<'D','E','L','E'>::value: esm.skipHSub(); - mIsDeleted = true; + isDeleted = true; break; default: esm.fail("Unknown subrecord"); @@ -47,12 +42,12 @@ namespace ESM if (!hasName) esm.fail("Missing NAME"); } - void StartScript::save(ESMWriter &esm) const + void StartScript::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNString("DATA", mData); esm.writeHNString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); } @@ -61,6 +56,5 @@ namespace ESM void StartScript::blank() { mData.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index e475abd86..ce2ff49e7 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -26,13 +26,9 @@ struct StartScript std::string mData; std::string mId; - bool mIsDeleted; - - StartScript(); - // Load a record and add it to the list - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index 9a146a370..eee7a50f5 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -8,32 +8,27 @@ namespace ESM { unsigned int Static::sRecordId = REC_STAT; - Static::Static() - : mIsDeleted(false) - {} - - void Static::load(ESMReader &esm) + void Static::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; @@ -43,10 +38,10 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); } - void Static::save(ESMWriter &esm) const + void Static::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); } @@ -59,6 +54,5 @@ namespace ESM void Static::blank() { mModel.clear(); - mIsDeleted = false; } } diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index f88ad671b..930cdb849 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -28,12 +28,8 @@ struct Static std::string mId, mModel; - bool mIsDeleted; - - Static(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 98302c13d..b0bc1dad6 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -8,30 +8,21 @@ namespace ESM { unsigned int Weapon::sRecordId = REC_WEAP; - Weapon::Weapon() - : mIsDeleted(false) - {} - - void Weapon::load(ESMReader &esm) + void Weapon::load(ESMReader &esm, bool &isDeleted) { - mIsDeleted = false; + isDeleted = false; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','E','L','E'>::value: - esm.skipHSub(); - mIsDeleted = true; - break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -51,6 +42,10 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEnchant = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); } @@ -58,14 +53,14 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - if (!hasData && !mIsDeleted) + if (!hasData && !isDeleted) esm.fail("Missing WPDT subrecord"); } - void Weapon::save(ESMWriter &esm) const + void Weapon::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - if (mIsDeleted) + if (isDeleted) { esm.writeHNCString("DELE", ""); return; @@ -98,7 +93,5 @@ namespace ESM mIcon.clear(); mEnchant.clear(); mScript.clear(); - - mIsDeleted = false; } } diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index ce61eeb72..eddcaee4f 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -69,12 +69,8 @@ struct Weapon std::string mId, mName, mModel, mIcon, mEnchant, mScript; - bool mIsDeleted; - - Weapon(); - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 62aa0452a..cf1db32ff 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -7,7 +7,8 @@ void ESM::ObjectState::load (ESMReader &esm) { mVersion = esm.getFormat(); - mRef.loadData(esm); + bool isDeleted; + mRef.loadData(esm, isDeleted); mHasLocals = 0; esm.getHNOT (mHasLocals, "HLOC"); From 0c002dd6de2e36968a2484e46ddccd549f9f02ba Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 21 Jul 2015 13:17:03 +0300 Subject: [PATCH 301/441] Load/read methods (for ESM records) accept a deleted flag in OpenMW (cherry picked from commit 67c8f95c4e85466a7c802f4cced117ade2378184) Conflicts: apps/openmw/mwworld/store.cpp --- apps/openmw/mwworld/cellreflist.hpp | 2 +- apps/openmw/mwworld/cellstore.cpp | 58 +++++++++++++++-------------- apps/openmw/mwworld/cellstore.hpp | 2 +- apps/openmw/mwworld/globals.cpp | 6 ++- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 5b3ad6262..2c5e01aaa 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -22,7 +22,7 @@ namespace MWWorld /// and the build will fail with an ugly three-way cyclic header dependence /// so we need to pass the instantiation of the method to the linker, when /// all methods are known. - void load (ESM::CellRef &ref, const MWWorld::ESMStore &esmStore); + void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); LiveRef *find (const std::string& name) { diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 03178001c..67de77ceb 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -146,7 +146,7 @@ namespace MWWorld { template - void CellRefList::load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore) + void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { const MWWorld::Store &store = esmStore.get(); @@ -157,7 +157,7 @@ namespace MWWorld LiveRef liveCellRef (ref, ptr); - if (ref.mIsDeleted) + if (deleted) liveCellRef.mData.setDeleted(true); if (iter != mList.end()) @@ -444,9 +444,10 @@ namespace MWWorld ESM::CellRef ref; // Get each reference in turn - while (mCell->getNextRef (esm[index], ref)) + bool deleted = false; + while (mCell->getNextRef (esm[index], ref, deleted)) { - if (ref.mIsDeleted) + if (deleted) continue; // Don't list reference if it was moved to a different cell. @@ -489,7 +490,8 @@ namespace MWWorld ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; // Get each reference in turn - while(mCell->getNextRef(esm[index], ref)) + bool deleted = false; + while(mCell->getNextRef(esm[index], ref, deleted)) { // Don't load reference if it was moved to a different cell. ESM::MovedCellRefTracker::const_iterator iter = @@ -498,7 +500,7 @@ namespace MWWorld continue; } - loadRef (ref, store); + loadRef (ref, deleted, store); } } @@ -507,7 +509,7 @@ namespace MWWorld { ESM::CellRef &ref = const_cast(*it); - loadRef (ref, store); + loadRef (ref, false, store); } } @@ -536,32 +538,32 @@ namespace MWWorld return Ptr(); } - void CellStore::loadRef (ESM::CellRef& ref, const ESMStore& store) + void CellStore::loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store) { Misc::StringUtils::toLower (ref.mRefID); switch (store.find (ref.mRefID)) { - case ESM::REC_ACTI: mActivators.load(ref, store); break; - case ESM::REC_ALCH: mPotions.load(ref,store); break; - case ESM::REC_APPA: mAppas.load(ref, store); break; - case ESM::REC_ARMO: mArmors.load(ref, store); break; - case ESM::REC_BOOK: mBooks.load(ref, store); break; - case ESM::REC_CLOT: mClothes.load(ref, store); break; - case ESM::REC_CONT: mContainers.load(ref, store); break; - case ESM::REC_CREA: mCreatures.load(ref, store); break; - case ESM::REC_DOOR: mDoors.load(ref, store); break; - case ESM::REC_INGR: mIngreds.load(ref, store); break; - case ESM::REC_LEVC: mCreatureLists.load(ref, store); break; - case ESM::REC_LEVI: mItemLists.load(ref, store); break; - case ESM::REC_LIGH: mLights.load(ref, store); break; - case ESM::REC_LOCK: mLockpicks.load(ref, store); break; - case ESM::REC_MISC: mMiscItems.load(ref, store); break; - case ESM::REC_NPC_: mNpcs.load(ref, store); break; - case ESM::REC_PROB: mProbes.load(ref, store); break; - case ESM::REC_REPA: mRepairs.load(ref, store); break; - case ESM::REC_STAT: mStatics.load(ref, store); break; - case ESM::REC_WEAP: mWeapons.load(ref, store); break; + case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; + case ESM::REC_ALCH: mPotions.load(ref, deleted,store); break; + case ESM::REC_APPA: mAppas.load(ref, deleted, store); break; + case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break; + case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break; + case ESM::REC_CLOT: mClothes.load(ref, deleted, store); break; + case ESM::REC_CONT: mContainers.load(ref, deleted, store); break; + case ESM::REC_CREA: mCreatures.load(ref, deleted, store); break; + case ESM::REC_DOOR: mDoors.load(ref, deleted, store); break; + case ESM::REC_INGR: mIngreds.load(ref, deleted, store); break; + case ESM::REC_LEVC: mCreatureLists.load(ref, deleted, store); break; + case ESM::REC_LEVI: mItemLists.load(ref, deleted, store); break; + case ESM::REC_LIGH: mLights.load(ref, deleted, store); break; + case ESM::REC_LOCK: mLockpicks.load(ref, deleted, store); break; + case ESM::REC_MISC: mMiscItems.load(ref, deleted, store); break; + case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; + case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; + case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; + case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; + case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; case 0: std::cerr << "Cell reference " + ref.mRefID + " not found!\n"; break; diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 6c4ba06f4..672b6046b 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -217,7 +217,7 @@ namespace MWWorld void loadRefs(const MWWorld::ESMStore &store, std::vector &esm); - void loadRef (ESM::CellRef& ref, const ESMStore& store); + void loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store); ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index 48b88cd13..4a406613d 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -91,7 +91,11 @@ namespace MWWorld if (type==ESM::REC_GLOB) { ESM::Global global; - global.load(reader); + bool isDeleted = false; + + // This readRecord() method is used when reading a saved game. + // Deleted globals can't appear there, so isDeleted will be ignored here. + global.load(reader, isDeleted); Misc::StringUtils::toLower(global.mId); Collection::iterator iter = mVariables.find (global.mId); From f4587e48f3b7a50ce2f0f1e067a378a0b5232ca8 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 21 Jul 2015 20:25:43 +0300 Subject: [PATCH 302/441] Load methods (for ESM records) accept a deleted flag in OpenCS (cherry picked from commit 13bb6be2383c808084eabed0cb1bfb3b9749e55a) Conflicts: apps/opencs/model/doc/savingstages.cpp apps/opencs/model/world/land.cpp apps/opencs/model/world/land.hpp apps/opencs/model/world/landtexture.cpp --- apps/opencs/model/doc/savingstages.cpp | 31 ++++-------- apps/opencs/model/doc/savingstages.hpp | 3 +- apps/opencs/model/world/cell.cpp | 6 +-- apps/opencs/model/world/cell.hpp | 2 +- apps/opencs/model/world/data.cpp | 6 ++- apps/opencs/model/world/idcollection.cpp | 3 -- apps/opencs/model/world/idcollection.hpp | 13 +++-- apps/opencs/model/world/infocollection.cpp | 6 ++- apps/opencs/model/world/land.cpp | 5 +- apps/opencs/model/world/land.hpp | 2 +- apps/opencs/model/world/landtexture.cpp | 10 ++-- apps/opencs/model/world/landtexture.hpp | 2 +- apps/opencs/model/world/pathgrid.cpp | 15 ++---- apps/opencs/model/world/pathgrid.hpp | 5 +- apps/opencs/model/world/record.cpp | 48 ------------------- apps/opencs/model/world/record.hpp | 39 --------------- apps/opencs/model/world/refcollection.cpp | 5 +- apps/opencs/model/world/refiddata.hpp | 11 +++-- apps/opencs/model/world/subcellcollection.hpp | 7 +-- 19 files changed, 60 insertions(+), 159 deletions(-) delete mode 100644 apps/opencs/model/world/idcollection.cpp diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 8a17ae031..546e8471c 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -106,10 +106,8 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message { // if the topic is deleted, we do not need to bother with INFO records. ESM::Dialogue dialogue = topic.get(); - dialogue.mIsDeleted = true; - writer.startRecord(dialogue.sRecordId); - dialogue.save(writer); + dialogue.save(writer, true); writer.endRecord(dialogue.sRecordId); return; } @@ -120,7 +118,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - if (topic.isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) + if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) { infoModified = true; break; @@ -130,7 +128,6 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message if (topic.isModified() || infoModified) { ESM::Dialogue dialogue = topic.get(); - if (infoModified && state != CSMWorld::RecordBase::State_Modified && state != CSMWorld::RecordBase::State_ModifiedOnly) { @@ -158,7 +155,6 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message { ESM::DialInfo info = iter->get(); info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); - info.mIsDeleted = (iter->mState == CSMWorld::RecordBase::State_Deleted); info.mPrev = ""; if (iter!=range.first) @@ -179,7 +175,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message } writer.startRecord (info.sRecordId); - info.save (writer); + info.save (writer, iter->mState == CSMWorld::RecordBase::State_Deleted); writer.endRecord (info.sRecordId); } } @@ -228,9 +224,7 @@ void CSMDoc::CollectionReferencesStage::perform (int stage, Messages& messages) const CSMWorld::Record& record = mDocument.getData().getReferences().getRecord (i); - if (record.mState==CSMWorld::RecordBase::State_Deleted || - record.mState==CSMWorld::RecordBase::State_Modified || - record.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (record.isModified() || record.mState == CSMWorld::RecordBase::State_Deleted) { std::string cellId = record.get().mOriginalCell.empty() ? record.get().mCell : record.get().mOriginalCell; @@ -299,8 +293,7 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) stream >> ignore >> cellRecord.mData.mX >> cellRecord.mData.mY; } - cellRecord.mIsDeleted = (cell.mState == CSMWorld::RecordBase::State_Deleted); - cellRecord.save (writer); + cellRecord.save (writer, cell.mState == CSMWorld::RecordBase::State_Deleted); // write references if (references!=mState.getSubRecords().end()) @@ -341,8 +334,7 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) writer.writeHNT ("CNDT", moved.mTarget, 8); } - refRecord.mIsDeleted = (ref.mState == CSMWorld::RecordBase::State_Deleted); - refRecord.save (writer); + refRecord.save (writer, false, false, ref.mState == CSMWorld::RecordBase::State_Deleted); } } } @@ -381,9 +373,8 @@ void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& message else record.mCell = record.mId; - record.mIsDeleted = (pathgrid.mState == CSMWorld::RecordBase::State_Deleted); writer.startRecord (record.sRecordId); - record.save (writer); + record.save (writer, pathgrid.mState == CSMWorld::RecordBase::State_Deleted); writer.endRecord (record.sRecordId); } } @@ -408,10 +399,8 @@ void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) if (land.isModified() || land.mState == CSMWorld::RecordBase::State_Deleted) { const CSMWorld::Land& record = land.get(); - - record.mLand->mIsDeleted = (land.mState == CSMWorld::RecordBase::State_Deleted); writer.startRecord (record.mLand->sRecordId); - record.mLand->save (writer); + record.mLand->save (writer, land.mState == CSMWorld::RecordBase::State_Deleted); writer.endRecord (record.mLand->sRecordId); } } @@ -436,10 +425,8 @@ void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& mess if (landTexture.isModified() || landTexture.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::LandTexture record = landTexture.get(); - record.mIsDeleted = (landTexture.mState == CSMWorld::RecordBase::State_Deleted); - writer.startRecord (record.sRecordId); - record.save (writer); + record.save (writer, landTexture.mState == CSMWorld::RecordBase::State_Deleted); writer.endRecord (record.sRecordId); } } diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index a7d9704b0..64afd0dd8 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -108,9 +108,8 @@ namespace CSMDoc state == CSMWorld::RecordBase::State_ModifiedOnly || state == CSMWorld::RecordBase::State_Deleted) { - CSMWorld::setRecordDeleted (record, state == CSMWorld::RecordBase::State_Deleted); writer.startRecord (record.sRecordId); - record.save (writer); + record.save (writer, state == CSMWorld::RecordBase::State_Deleted); writer.endRecord (record.sRecordId); } } diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index b8c78eb09..93f3c500d 100644 --- a/apps/opencs/model/world/cell.cpp +++ b/apps/opencs/model/world/cell.cpp @@ -2,12 +2,12 @@ #include -void CSMWorld::Cell::load (ESM::ESMReader &esm) +void CSMWorld::Cell::load (ESM::ESMReader &esm, bool &isDeleted) { - ESM::Cell::load (esm, false); + ESM::Cell::load (esm, isDeleted, false); mId = mName; - if (!(mData.mFlags & Interior)) + if (isExterior()) { std::ostringstream stream; stream << "#" << mData.mX << " " << mData.mY; diff --git a/apps/opencs/model/world/cell.hpp b/apps/opencs/model/world/cell.hpp index f393e2cf9..160610874 100644 --- a/apps/opencs/model/world/cell.hpp +++ b/apps/opencs/model/world/cell.hpp @@ -16,7 +16,7 @@ namespace CSMWorld { std::string mId; - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index d6587ada2..22ad0048b 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1109,9 +1109,11 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) case ESM::REC_DIAL: { ESM::Dialogue record; - record.load (*mReader); + bool isDeleted = false; - if (record.mIsDeleted) + record.load (*mReader, isDeleted); + + if (isDeleted) { // record vector can be shuffled around which would make pointer to record invalid mDialogue = 0; diff --git a/apps/opencs/model/world/idcollection.cpp b/apps/opencs/model/world/idcollection.cpp deleted file mode 100644 index 9571ed773..000000000 --- a/apps/opencs/model/world/idcollection.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "idcollection.hpp" - - diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 9d3ec990e..c79b4e020 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -12,7 +12,7 @@ namespace CSMWorld template > class IdCollection : public Collection { - virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader); + virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted); public: @@ -34,21 +34,24 @@ namespace CSMWorld template void IdCollection::loadRecord (ESXRecordT& record, - ESM::ESMReader& reader) + ESM::ESMReader& reader, + bool& isDeleted) { - record.load (reader); + record.load (reader, isDeleted); } template int IdCollection::load (ESM::ESMReader& reader, bool base) { ESXRecordT record; - loadRecord (record, reader); + bool isDeleted = false; + + loadRecord (record, reader, isDeleted); std::string id = IdAccessorT().getId (record); int index = this->searchId (id); - if (isRecordDeleted(record)) + if (isDeleted) { if (index==-1) { diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index e2cfea393..f5ec4d458 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -107,10 +107,12 @@ bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vectorload(esm, isDeleted); std::ostringstream stream; stream << "#" << mX << " " << mY; - mId = stream.str(); } } diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index 22cedb56d..fd97ac2c5 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -16,7 +16,7 @@ namespace CSMWorld std::string mId; /// Loads the metadata and ID - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index e7772129c..9e8dcff9f 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -4,12 +4,14 @@ namespace CSMWorld { - - void LandTexture::load(ESM::ESMReader &esm) + void LandTexture::load(ESM::ESMReader &esm, bool &isDeleted) { - ESM::LandTexture::load(esm); + ESM::LandTexture::load(esm, isDeleted); + int plugin = esm.getIndex(); - mPluginIndex = esm.getIndex(); + std::ostringstream stream; + stream << mIndex << "_" << plugin; + mId = stream.str(); } } diff --git a/apps/opencs/model/world/landtexture.hpp b/apps/opencs/model/world/landtexture.hpp index c0b6eeba9..91459eee2 100644 --- a/apps/opencs/model/world/landtexture.hpp +++ b/apps/opencs/model/world/landtexture.hpp @@ -12,7 +12,7 @@ namespace CSMWorld { int mPluginIndex; - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/pathgrid.cpp b/apps/opencs/model/world/pathgrid.cpp index 5c66e7d8e..c995bd8f0 100644 --- a/apps/opencs/model/world/pathgrid.cpp +++ b/apps/opencs/model/world/pathgrid.cpp @@ -4,33 +4,28 @@ #include -void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, const IdCollection& cells) +void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells) { - load (esm); + load (esm, isDeleted); // correct ID if (!mId.empty() && mId[0]!='#' && cells.searchId (mId)==-1) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } } -void CSMWorld::Pathgrid::load (ESM::ESMReader &esm) +void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted) { - ESM::Pathgrid::load (esm); + ESM::Pathgrid::load (esm, isDeleted); + mId = mCell; if (mCell.empty()) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } - else - mId = mCell; } diff --git a/apps/opencs/model/world/pathgrid.hpp b/apps/opencs/model/world/pathgrid.hpp index 7e7b7c3bb..22d01b071 100644 --- a/apps/opencs/model/world/pathgrid.hpp +++ b/apps/opencs/model/world/pathgrid.hpp @@ -20,9 +20,8 @@ namespace CSMWorld { std::string mId; - void load (ESM::ESMReader &esm, const IdCollection& cells); - - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp index 699d68ba7..f13a36afc 100644 --- a/apps/opencs/model/world/record.cpp +++ b/apps/opencs/model/world/record.cpp @@ -18,51 +18,3 @@ bool CSMWorld::RecordBase::isModified() const { return mState==State_Modified || mState==State_ModifiedOnly; } - -template<> -bool CSMWorld::isRecordDeleted(const CSMWorld::Land &land) -{ - return land.mLand->mIsDeleted; -} - -template<> -bool CSMWorld::isRecordDeleted(const ESM::GameSetting &setting) -{ - return false; -} - -template<> -bool CSMWorld::isRecordDeleted(const ESM::MagicEffect &effect) -{ - return false; -} - -template<> -bool CSMWorld::isRecordDeleted(const ESM::Skill &skill) -{ - return false; -} - -template<> -void CSMWorld::setRecordDeleted(CSMWorld::Land &land, bool isDeleted) -{ - land.mLand->mIsDeleted = isDeleted; -} - -template<> -void CSMWorld::setRecordDeleted(ESM::GameSetting &setting, bool isDeleted) -{ - // GameSetting doesn't have a Deleted flag -} - -template<> -void CSMWorld::setRecordDeleted(ESM::MagicEffect &effect, bool isDeleted) -{ - // MagicEffect doesn't have a Deleted flag -} - -template<> -void CSMWorld::setRecordDeleted(ESM::Skill &skill, bool isDeleted) -{ - // Skill doesn't have a Deleted flag -} diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index 8e39cd705..3362f9f96 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -3,12 +3,6 @@ #include -#include -#include -#include - -#include "land.hpp" - namespace CSMWorld { struct RecordBase @@ -160,39 +154,6 @@ namespace CSMWorld mState = State_Erased; } } - - // Not all records can be deleted (may be changed in the future), - // so we need to use a separate method to check whether a record is deleted or not. - template - bool isRecordDeleted(const ESXRecordT &record) - { - return record.mIsDeleted; - } - - template<> - bool isRecordDeleted(const Land &land); - template<> - bool isRecordDeleted(const ESM::GameSetting &setting); - template<> - bool isRecordDeleted(const ESM::MagicEffect &effect); - template<> - bool isRecordDeleted(const ESM::Skill &skill); - - // ... and also a separate method for setting the deleted flag of a record - template - void setRecordDeleted(ESXRecordT &record, bool isDeleted = false) - { - record.mIsDeleted = isDeleted; - } - - template<> - void setRecordDeleted(Land &land, bool isDeleted); - template<> - void setRecordDeleted(ESM::GameSetting &setting, bool isDeleted); - template<> - void setRecordDeleted(ESM::MagicEffect &effect, bool isDeleted); - template<> - void setRecordDeleted(ESM::Skill &skill, bool isDeleted); } #endif diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 0b25f2711..65251a81d 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -20,9 +20,10 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool CellRef ref; ESM::MovedCellRef mref; + bool isDeleted = false; // hack to initialise mindex - while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, true, &mref)) + while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, isDeleted, true, &mref)) { // Keep mOriginalCell empty when in modified (as an indicator that the // original cell will always be equal the current cell). @@ -78,7 +79,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool break; } - if (ref.mIsDeleted) + if (isDeleted) { if (iter==cache.end()) { diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index a2922958a..59cad6a66 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -129,7 +129,9 @@ namespace CSMWorld int RefIdDataContainer::load (ESM::ESMReader& reader, bool base) { RecordT record; - record.load(reader); + bool isDeleted = false; + + record.load(reader, isDeleted); int index = 0; int numRecords = static_cast(mContainer.size()); @@ -141,7 +143,7 @@ namespace CSMWorld } } - if (record.mIsDeleted) + if (isDeleted) { if (index == numRecords) { @@ -197,13 +199,12 @@ namespace CSMWorld void RefIdDataContainer::save (int index, ESM::ESMWriter& writer) const { Record record = mContainer.at(index); - RecordT esmRecord = record.get(); if (record.isModified() || record.mState == RecordBase::State_Deleted) { - esmRecord.mIsDeleted = (record.mState == RecordBase::State_Deleted); + RecordT esmRecord = record.get(); writer.startRecord(esmRecord.sRecordId); - esmRecord.save(writer); + esmRecord.save(writer, record.mState == RecordBase::State_Deleted); writer.endRecord(esmRecord.sRecordId); } } diff --git a/apps/opencs/model/world/subcellcollection.hpp b/apps/opencs/model/world/subcellcollection.hpp index df1f8a12e..496cb0643 100644 --- a/apps/opencs/model/world/subcellcollection.hpp +++ b/apps/opencs/model/world/subcellcollection.hpp @@ -20,7 +20,7 @@ namespace CSMWorld { const IdCollection& mCells; - virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader); + virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted); public: @@ -29,9 +29,10 @@ namespace CSMWorld template void SubCellCollection::loadRecord (ESXRecordT& record, - ESM::ESMReader& reader) + ESM::ESMReader& reader, + bool& isDeleted) { - record.load (reader, mCells); + record.load (reader, isDeleted, mCells); } template From 0c4dd483944f85c27a0df734c9969bc160877053 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 21 Jul 2015 20:47:02 +0300 Subject: [PATCH 303/441] Load methods (for ESM records) accept a deleted flag in ESMTool (cherry picked from commit 8243fb2479a18cd47e03e9ffcff87020a9c1446f) Conflicts: apps/esmtool/record.cpp --- apps/esmtool/esmtool.cpp | 14 ++++---- apps/esmtool/record.cpp | 74 ++++++++++++++++++++-------------------- apps/esmtool/record.hpp | 9 +++-- 3 files changed, 52 insertions(+), 45 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index abb05f3fc..512a348ae 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -26,7 +26,8 @@ struct ESMData std::vector masters; std::deque mRecords; - std::map > mCellRefs; + // Value: (Reference, Deleted flag) + std::map > > mCellRefs; std::map mRecordStats; static const std::set sLabeledRec; @@ -250,10 +251,11 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) ESM::CellRef ref; if(!quiet) std::cout << " References:\n"; - while(cell.getNextRef(esm, ref)) + bool deleted = false; + while(cell.getNextRef(esm, ref, deleted)) { if (save) { - info.data.mCellRefs[&cell].push_back(ref); + info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted)); } if(quiet) continue; @@ -268,7 +270,7 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) std::cout << " Uses/health: '" << ref.mChargeInt << "'\n"; std::cout << " Gold value: '" << ref.mGoldValue << "'\n"; std::cout << " Blocked: '" << static_cast(ref.mReferenceBlocked) << "'" << std::endl; - std::cout << " Deleted: " << ref.mIsDeleted << std::endl; + std::cout << " Deleted: " << deleted << std::endl; if (!ref.mKey.empty()) std::cout << " Key: '" << ref.mKey << "'" << std::endl; } @@ -496,11 +498,11 @@ int clone(Arguments& info) if (name.val == ESM::REC_CELL) { ESM::Cell *ptr = &record->cast()->get(); if (!info.data.mCellRefs[ptr].empty()) { - typedef std::deque RefList; + typedef std::deque > RefList; RefList &refs = info.data.mCellRefs[ptr]; for (RefList::iterator refIt = refs.begin(); refIt != refs.end(); ++refIt) { - refIt->save(esm); + refIt->first.save(esm, refIt->second); } } } diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index ebb18c625..077927c99 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -405,7 +405,7 @@ void Record::print() std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -420,7 +420,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; printEffectList(mData.mEffects); - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -449,7 +449,7 @@ void Record::print() if (pit->mFemale != "") std::cout << " Female Name: " << pit->mFemale << std::endl; } - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -464,7 +464,7 @@ void Record::print() std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -478,7 +478,7 @@ void Record::print() std::cout << " Part: " << meshPartLabel(mData.mData.mPart) << " (" << (int)mData.mData.mPart << ")" << std::endl; std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -507,7 +507,7 @@ void Record::print() { std::cout << " Text: [skipped]" << std::endl; } - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -519,7 +519,7 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit) std::cout << " Power: " << *pit << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -548,7 +548,7 @@ void Record::print() std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl; std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } @@ -571,7 +571,7 @@ void Record::print() for (int i = 0; i != 5; i++) std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1]) << " (" << mData.mData.mSkills[i][1] << ")" << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -598,7 +598,7 @@ void Record::print() if (pit->mFemale != "") std::cout << " Female Name: " << pit->mFemale << std::endl; } - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -614,7 +614,7 @@ void Record::print() for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit) std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount << " Item: " << cit->mItem.toString() << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -681,7 +681,7 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -689,7 +689,7 @@ void Record::print() { std::cout << " Type: " << dialogTypeLabel(mData.mType) << " (" << (int)mData.mType << ")" << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; // Sadly, there are no DialInfos, because the loader dumps as it // loads, rather than loading and then dumping. :-( Anyone mind if // I change this? @@ -706,7 +706,7 @@ void Record::print() std::cout << " Script: " << mData.mScript << std::endl; std::cout << " OpenSound: " << mData.mOpenSound << std::endl; std::cout << " CloseSound: " << mData.mCloseSound << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -718,7 +718,7 @@ void Record::print() std::cout << " Charge: " << mData.mData.mCharge << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl; printEffectList(mData.mEffects); - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -752,14 +752,14 @@ void Record::print() std::map::iterator rit; for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit) std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " " << mData.mValue << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -826,7 +826,7 @@ void Record::print() std::cout << " Result Script: [skipped]" << std::endl; } } - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -850,7 +850,7 @@ void Record::print() std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i]) << " (" << mData.mData.mAttributes[i] << ")" << std::endl; } - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -867,7 +867,7 @@ void Record::print() std::cout << " Unknown1: " << data->mUnk1 << std::endl; std::cout << " Unknown2: " << data->mUnk2 << std::endl; } - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -880,7 +880,7 @@ void Record::print() for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Creature: Level: " << iit->mLevel << " Creature: " << iit->mId << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -893,7 +893,7 @@ void Record::print() for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Inventory: Level: " << iit->mLevel << " Item: " << iit->mId << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -914,7 +914,7 @@ void Record::print() std::cout << " Duration: " << mData.mData.mTime << std::endl; std::cout << " Radius: " << mData.mData.mRadius << std::endl; std::cout << " Color: " << mData.mData.mColor << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -929,7 +929,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -944,7 +944,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -959,7 +959,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -968,7 +968,7 @@ void Record::print() std::cout << " Id: " << mData.mId << std::endl; std::cout << " Index: " << mData.mIndex << std::endl; std::cout << " Texture: " << mData.mTexture << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1019,7 +1019,7 @@ void Record::print() std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Is Key: " << mData.mData.mIsKey << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1106,7 +1106,7 @@ void Record::print() for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1142,7 +1142,7 @@ void Record::print() i++; } - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1184,7 +1184,7 @@ void Record::print() for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit) std::cout << " Power: " << *sit << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1245,7 +1245,7 @@ void Record::print() std::cout << " Script: [skipped]" << std::endl; } - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1269,7 +1269,7 @@ void Record::print() std::cout << " Sound: " << mData.mSound << std::endl; std::cout << " Type: " << soundTypeLabel(mData.mType) << " (" << mData.mType << ")" << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1280,7 +1280,7 @@ void Record::print() if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0) std::cout << " Range: " << (int)mData.mData.mMinRange << " - " << (int)mData.mData.mMaxRange << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1292,7 +1292,7 @@ void Record::print() std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl; std::cout << " Cost: " << mData.mData.mCost << std::endl; printEffectList(mData.mEffects); - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1300,7 +1300,7 @@ void Record::print() { std::cout << " Start Script: " << mData.mId << std::endl; std::cout << " Start Data: " << mData.mData << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1341,7 +1341,7 @@ void Record::print() if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0) std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-" << (int)mData.mData.mThrust[1] << std::endl; - std::cout << " Deleted: " << mData.mIsDeleted << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index c55b2ba8f..dca38409f 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -67,8 +67,13 @@ namespace EsmTool class Record : public RecordBase { T mData; + bool mIsDeleted; public: + Record() + : mIsDeleted(false) + {} + std::string getId() const { return mData.mId; } @@ -78,11 +83,11 @@ namespace EsmTool } void save(ESM::ESMWriter &esm) { - mData.save(esm); + mData.save(esm, mIsDeleted); } void load(ESM::ESMReader &esm) { - mData.load(esm); + mData.load(esm, mIsDeleted); } void print(); From 38e99c9cd402a806315cd9a0da3aaa2eb5fea6f5 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 21 Jul 2015 21:49:36 +0300 Subject: [PATCH 304/441] Load methods (for ESM records) accept a deleted flag in ESSImporter (cherry picked from commit 0c6ab6cc9449e14fe30d1f9baa3424b92eee9ba8) --- apps/essimporter/converter.cpp | 4 +++- apps/essimporter/converter.hpp | 29 +++++++++++++++++++++------- apps/essimporter/importacdt.cpp | 3 ++- apps/essimporter/importinventory.cpp | 3 ++- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 5c4c0e62e..b372012f9 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -134,7 +134,9 @@ namespace ESSImport void ConvertCell::read(ESM::ESMReader &esm) { ESM::Cell cell; - cell.load(esm, false); + bool isDeleted = false; + + cell.load(esm, isDeleted, false); // I wonder what 0x40 does? if (cell.isExterior() && cell.mData.mFlags & 0x20) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 80d5899ec..61155c1ea 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -51,6 +51,8 @@ public: void setContext(Context& context) { mContext = &context; } + /// @note The load method of ESM records accept the deleted flag as a parameter. + /// I don't know can the DELE sub-record appear in saved games, so the deleted flag will be ignored. virtual void read(ESM::ESMReader& esm) { } @@ -76,7 +78,9 @@ public: virtual void read(ESM::ESMReader& esm) { T record; - record.load(esm); + bool isDeleted = false; + + record.load(esm, isDeleted); mRecords[record.mId] = record; } @@ -100,7 +104,9 @@ public: virtual void read(ESM::ESMReader &esm) { ESM::NPC npc; - npc.load(esm); + bool isDeleted = false; + + npc.load(esm, isDeleted); if (npc.mId != "player") { // Handles changes to the NPC struct, but since there is no index here @@ -136,7 +142,9 @@ public: { // See comment in ConvertNPC ESM::Creature creature; - creature.load(esm); + bool isDeleted = false; + + creature.load(esm, isDeleted); mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature; } }; @@ -151,7 +159,9 @@ public: virtual void read(ESM::ESMReader &esm) { ESM::Global global; - global.load(esm); + bool isDeleted = false; + + global.load(esm, isDeleted); if (Misc::StringUtils::ciEqual(global.mId, "gamehour")) mContext->mHour = global.mValue.getFloat(); if (Misc::StringUtils::ciEqual(global.mId, "day")) @@ -170,8 +180,9 @@ public: virtual void read(ESM::ESMReader &esm) { ESM::Class class_; - class_.load(esm); + bool isDeleted = false; + class_.load(esm, isDeleted); if (class_.mId == "NEWCLASSID_CHARGEN") mContext->mCustomPlayerClassName = class_.mName; @@ -185,7 +196,9 @@ public: virtual void read(ESM::ESMReader &esm) { ESM::Book book; - book.load(esm); + bool isDeleted = false; + + book.load(esm, isDeleted); if (book.mData.mSkillID == -1) mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId)); @@ -362,7 +375,9 @@ public: virtual void read(ESM::ESMReader& esm) { ESM::Faction faction; - faction.load(esm); + bool isDeleted = false; + + faction.load(esm, isDeleted); std::string id = Misc::StringUtils::toLower(faction.mId); for (std::map::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp index 9d881515d..1602aa784 100644 --- a/apps/essimporter/importacdt.cpp +++ b/apps/essimporter/importacdt.cpp @@ -18,7 +18,8 @@ namespace ESSImport if (esm.isNextSub("MNAM")) esm.skipHSub(); - ESM::CellRef::loadData(esm); + bool isDeleted = false; + ESM::CellRef::loadData(esm, isDeleted); mHasACDT = false; if (esm.isNextSub("ACDT")) diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index d27cd5c8c..3ec640d3d 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -32,7 +32,8 @@ namespace ESSImport item.mSCRI.load(esm); // for XSOL and XCHG seen so far, but probably others too - item.ESM::CellRef::loadData(esm); + bool isDeleted = false; + item.ESM::CellRef::loadData(esm, isDeleted); int charge=-1; esm.getHNOT(charge, "XHLT"); From 2abdeef2e0bd9a2851d2cf2bb609c38ee8356707 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Wed, 22 Jul 2015 22:02:01 +0300 Subject: [PATCH 305/441] Remove unused includes (cherry picked from commit daaff1284e26d13358b130d7b44721ced210066d) Conflicts: apps/openmw/mwworld/store.cpp components/esm/util.hpp --- apps/opencs/model/world/idcollection.hpp | 1 - components/esm/loaddial.hpp | 1 - components/esm/util.hpp | 5 ----- 3 files changed, 7 deletions(-) diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index c79b4e020..ea6eefb88 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -4,7 +4,6 @@ #include #include "collection.hpp" -#include "record.hpp" namespace CSMWorld { diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index e517fc86f..b80cbd74c 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include "loadinfo.hpp" diff --git a/components/esm/util.hpp b/components/esm/util.hpp index 13a1c6544..bb7f3cf7c 100644 --- a/components/esm/util.hpp +++ b/components/esm/util.hpp @@ -1,14 +1,9 @@ #ifndef OPENMW_ESM_UTIL_H #define OPENMW_ESM_UTIL_H -#include - #include #include -#include "esmreader.hpp" -#include "esmwriter.hpp" - namespace ESM { From 3821bffbfaa73b5086859459c874d5f659408467 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Sun, 26 Jul 2015 13:54:36 +0300 Subject: [PATCH 306/441] Fix missing break in switch statement (cherry picked from commit 9a8ca819073e54406daafd04319299429c089c4e) --- components/esm/loadregn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index b04e6ee3b..add821c3e 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -22,7 +22,6 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -69,6 +68,7 @@ namespace ESM case ESM::FourCC<'D','E','L','E'>::value: esm.skipHSub(); isDeleted = true; + break; default: esm.fail("Unknown subrecord"); break; From d789d9369c1bcf11d63c650aef64b8f209a23581 Mon Sep 17 00:00:00 2001 From: Stanislav Bas Date: Tue, 28 Jul 2015 15:04:22 +0300 Subject: [PATCH 307/441] Make saving of deleted ESM records more consistent (cherry picked from commit f9b0b7ede5eb2bc8ef62fce47c495247b04ebd78) --- components/esm/cellref.cpp | 67 +++++++++++++++++-------------------- components/esm/cellref.hpp | 3 -- components/esm/loadbsgn.cpp | 5 +-- components/esm/loadcell.cpp | 18 ++++------ components/esm/loadland.cpp | 1 + components/esm/loadltex.cpp | 8 ++--- components/esm/loadpgrd.cpp | 12 +++---- components/esm/loadregn.cpp | 4 ++- components/esm/loadscpt.cpp | 1 + components/esm/loadsndg.cpp | 11 +++--- components/esm/loadsscr.cpp | 20 ++++++----- 11 files changed, 71 insertions(+), 79 deletions(-) diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index a61a16ca4..d4ef29e04 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -24,33 +24,6 @@ void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const } -void ESM::CellRef::clearData() -{ - mScale = 1; - mOwner.clear(); - mGlobalVariable.clear(); - mSoul.clear(); - mFaction.clear(); - mFactionRank = -2; - mChargeInt = -1; - mEnchantmentCharge = -1; - mGoldValue = 0; - mDestCell.clear(); - mLockLevel = 0; - mKey.clear(); - mTrap.clear(); - mReferenceBlocked = -1; - mTeleport = false; - - for (int i=0; i<3; ++i) - { - mDoorDest.pos[i] = 0; - mDoorDest.rot[i] = 0; - mPos.pos[i] = 0; - mPos.rot[i] = 0; - } -} - void ESM::CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum) { loadId(esm, wideRefNum); @@ -66,6 +39,8 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) if (esm.isNextSub ("NAM0")) esm.skipHSub(); + blank(); + mRefNum.load (esm, wideRefNum); mRefID = esm.getHNString ("NAME"); @@ -75,8 +50,6 @@ void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) { isDeleted = false; - clearData(); - bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) { @@ -153,6 +126,11 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool esm.writeHNCString("NAME", mRefID); + if (isDeleted) { + esm.writeHNCString("DELE", ""); + return; + } + if (mScale != 1.0) { esm.writeHNT("XSCL", mScale); } @@ -197,18 +175,35 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool if (!inInventory) esm.writeHNT("DATA", mPos, 24); - - if (isDeleted) - { - esm.writeHNCString("DELE", ""); - } } void ESM::CellRef::blank() { mRefNum.unset(); - mRefID.clear(); - clearData(); + mRefID.clear(); + mScale = 1; + mOwner.clear(); + mGlobalVariable.clear(); + mSoul.clear(); + mFaction.clear(); + mFactionRank = -2; + mChargeInt = -1; + mEnchantmentCharge = -1; + mGoldValue = 0; + mDestCell.clear(); + mLockLevel = 0; + mKey.clear(); + mTrap.clear(); + mReferenceBlocked = -1; + mTeleport = false; + + for (int i=0; i<3; ++i) + { + mDoorDest.pos[i] = 0; + mDoorDest.rot[i] = 0; + mPos.pos[i] = 0; + mPos.rot[i] = 0; + } } bool ESM::operator== (const RefNum& left, const RefNum& right) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index a9edd291e..c371a4f01 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -33,10 +33,7 @@ namespace ESM class CellRef { - void clearData(); - public: - // Reference number // Note: Currently unused for items in containers RefNum mRefNum; diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 56dc1897c..0413a8610 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -52,12 +52,13 @@ namespace ESM void BirthSign::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + if (isDeleted) { esm.writeHNCString("DELE", ""); + return; } - - esm.writeHNCString("NAME", mId); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("TNAM", mTexture); esm.writeHNOCString("DESC", mDescription); diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 2d8daa584..703a4f4cb 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -62,7 +62,8 @@ namespace ESM { isDeleted = false; - bool hasName = false; + blank(); + bool hasData = false; bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) @@ -72,7 +73,6 @@ namespace ESM { case ESM::FourCC<'N','A','M','E'>::value: mName = esm.getHString(); - hasName = true; break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mData, 12); @@ -89,20 +89,12 @@ namespace ESM } } - if (!hasName) - esm.fail("Missing NAME subrecord"); if (!hasData) esm.fail("Missing DATA subrecord"); } void Cell::loadCell(ESMReader &esm, bool saveContext) { - mWater = 0.0f; - mWaterInt = false; - mMapColor = 0; - mRegion.clear(); - mRefNumCounter = 0; - bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) { @@ -117,6 +109,7 @@ namespace ESM break; case ESM::FourCC<'W','H','G','T'>::value: esm.getHT(mWater); + mWaterInt = false; break; case ESM::FourCC<'A','M','B','I'>::value: esm.getHT(mAmbi); @@ -153,14 +146,15 @@ namespace ESM void Cell::save(ESMWriter &esm, bool isDeleted) const { - esm.writeHNCString("NAME", mName); + esm.writeHNOCString("NAME", mName); + esm.writeHNT("DATA", mData, 12); if (isDeleted) { esm.writeHNCString("DELE", ""); + return; } - esm.writeHNT("DATA", mData, 12); if (mData.mFlags & Interior) { if (mWaterInt) { diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 65e30f00f..13f65ea43 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -169,6 +169,7 @@ namespace ESM if (isDeleted) { esm.writeHNCString("DELE", ""); + return; } if (mLandData) diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index cf026dbf1..e9cd4578d 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -47,14 +47,14 @@ namespace ESM } void LandTexture::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + esm.writeHNT("INTV", mIndex); + esm.writeHNCString("DATA", mTexture); + if (isDeleted) { esm.writeHNCString("DELE", ""); } - - esm.writeHNCString("NAME", mId); - esm.writeHNT("INTV", mIndex); - esm.writeHNCString("DATA", mTexture); } void LandTexture::blank() diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 8027be91c..95e6a906f 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -43,20 +43,18 @@ namespace ESM int edgeCount = 0; bool hasData = false; - bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mCell = esm.getHString(); + break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mData, 12); hasData = true; break; - case ESM::FourCC<'N','A','M','E'>::value: - mCell = esm.getHString(); - hasName = true; - break; case ESM::FourCC<'P','G','R','P'>::value: { esm.getSubHeader(); @@ -125,14 +123,12 @@ namespace ESM if (!hasData) esm.fail("Missing DATA subrecord"); - if (!hasName) - esm.fail("Missing NAME subrecord"); } void Pathgrid::save(ESMWriter &esm, bool isDeleted) const { - esm.writeHNT("DATA", mData, 12); esm.writeHNCString("NAME", mCell); + esm.writeHNT("DATA", mData, 12); if (isDeleted) { diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index add821c3e..9f3089763 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -81,12 +81,14 @@ namespace ESM void Region::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + if (isDeleted) { esm.writeHNCString("DELE", ""); + return; } - esm.writeHNString("NAME", mId); esm.writeHNOCString("FNAM", mName); if (esm.getVersion() == VER_12) diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 09bf7f125..b36cf7ecf 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -123,6 +123,7 @@ namespace ESM if (isDeleted) { esm.writeHNCString("DELE", ""); + return; } if (!mVarNames.empty()) diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index 400b1072b..189cc97df 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -51,14 +51,17 @@ namespace ESM void SoundGenerator::save(ESMWriter &esm, bool isDeleted) const { esm.writeHNCString("NAME", mId); - esm.writeHNT("DATA", mType, 4); - esm.writeHNOCString("CNAM", mCreature); - esm.writeHNOCString("SNAM", mSound); - + if (isDeleted) { esm.writeHNCString("DELE", ""); + return; } + + esm.writeHNT("DATA", mType, 4); + esm.writeHNOCString("CNAM", mCreature); + esm.writeHNOCString("SNAM", mSound); + } void SoundGenerator::blank() diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index 6af6c96dc..a211a99bf 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -19,14 +19,14 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'D','A','T','A'>::value: - mData = esm.getHString(); - hasData = true; - break; case ESM::FourCC<'N','A','M','E'>::value: mId = esm.getHString(); hasName = true; break; + case ESM::FourCC<'D','A','T','A'>::value: + mData = esm.getHString(); + hasData = true; + break; case ESM::FourCC<'D','E','L','E'>::value: esm.skipHSub(); isDeleted = true; @@ -37,20 +37,22 @@ namespace ESM } } - if (!hasData) - esm.fail("Missing DATA"); if (!hasName) esm.fail("Missing NAME"); + if (!hasData && !isDeleted) + esm.fail("Missing DATA"); } void StartScript::save(ESMWriter &esm, bool isDeleted) const { - esm.writeHNString("DATA", mData); - esm.writeHNString("NAME", mId); - + esm.writeHNCString("NAME", mId); if (isDeleted) { esm.writeHNCString("DELE", ""); } + else + { + esm.writeHNString("DATA", mData); + } } void StartScript::blank() From 41ee50c2b7f0f0811f5c0fa94ae2600cc663fbf3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Nov 2015 18:07:18 +0100 Subject: [PATCH 308/441] Move common subrecord definitions (NAME, DELE) to defs.hpp (cherry picked from commit fc8e40889df58bc18a54082799699c6151d41343) --- components/esm/cellref.cpp | 2 +- components/esm/debugprofile.cpp | 4 ++-- components/esm/defs.hpp | 7 +++++++ components/esm/filter.cpp | 4 ++-- components/esm/loadacti.cpp | 4 ++-- components/esm/loadalch.cpp | 4 ++-- components/esm/loadappa.cpp | 4 ++-- components/esm/loadarmo.cpp | 4 ++-- components/esm/loadbody.cpp | 4 ++-- components/esm/loadbook.cpp | 4 ++-- components/esm/loadbsgn.cpp | 4 ++-- components/esm/loadcell.cpp | 4 ++-- components/esm/loadclas.cpp | 4 ++-- components/esm/loadclot.cpp | 4 ++-- components/esm/loadcont.cpp | 4 ++-- components/esm/loadcrea.cpp | 4 ++-- components/esm/loaddial.cpp | 2 +- components/esm/loaddoor.cpp | 4 ++-- components/esm/loadench.cpp | 4 ++-- components/esm/loadfact.cpp | 4 ++-- components/esm/loadinfo.cpp | 4 ++-- components/esm/loadingr.cpp | 4 ++-- components/esm/loadland.cpp | 2 +- components/esm/loadlevlist.cpp | 4 ++-- components/esm/loadligh.cpp | 4 ++-- components/esm/loadlock.cpp | 4 ++-- components/esm/loadltex.cpp | 4 ++-- components/esm/loadmisc.cpp | 4 ++-- components/esm/loadnpc.cpp | 4 ++-- components/esm/loadpgrd.cpp | 4 ++-- components/esm/loadprob.cpp | 4 ++-- components/esm/loadrace.cpp | 4 ++-- components/esm/loadregn.cpp | 4 ++-- components/esm/loadrepa.cpp | 4 ++-- components/esm/loadscpt.cpp | 2 +- components/esm/loadsndg.cpp | 4 ++-- components/esm/loadsoun.cpp | 4 ++-- components/esm/loadspel.cpp | 4 ++-- components/esm/loadsscr.cpp | 4 ++-- components/esm/loadstat.cpp | 4 ++-- components/esm/loadweap.cpp | 4 ++-- 41 files changed, 83 insertions(+), 76 deletions(-) diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index d4ef29e04..76a82fe23 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -108,7 +108,7 @@ void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) case ESM::FourCC<'N','A','M','0'>::value: esm.skipHSub(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 56be91e71..66e338d0c 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -15,7 +15,7 @@ void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); break; case ESM::FourCC<'D','E','S','C'>::value: @@ -27,7 +27,7 @@ void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) case ESM::FourCC<'F','L','A','G'>::value: esm.getHT(mFlags); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 7ef8102c2..9dc088596 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -124,5 +124,12 @@ enum RecNameInts REC_DBGP = FourCC<'D','B','G','P'>::value ///< only used in project files }; +/// Common subrecords +enum SubRecNameInts +{ + SREC_DELE = ESM::FourCC<'D','E','L','E'>::value, + SREC_NAME = ESM::FourCC<'N','A','M','E'>::value +}; + } #endif diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 5b33c6683..c3658f152 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -16,7 +16,7 @@ void ESM::Filter::load (ESMReader& esm, bool &isDeleted) uint32_t name = esm.retSubName().val; switch (name) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); break; case ESM::FourCC<'F','I','L','T'>::value: @@ -25,7 +25,7 @@ void ESM::Filter::load (ESMReader& esm, bool &isDeleted) case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index 14db45c34..b9934d3d3 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -18,7 +18,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -31,7 +31,7 @@ namespace ESM case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index ceff4ba7d..204904502 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -21,7 +21,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -44,7 +44,7 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEffects.add(esm); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index 7a77ba421..490881fae 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -39,7 +39,7 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index 600c417be..f2526554a 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -51,7 +51,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -77,7 +77,7 @@ namespace ESM case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index 20d6b35cf..09113e8d1 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -33,7 +33,7 @@ namespace ESM esm.getHT(mData, 4); hasData = true; break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index b08b12f50..617040be4 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -45,7 +45,7 @@ namespace ESM case ESM::FourCC<'T','E','X','T'>::value: mText = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 0413a8610..9a4d98bb7 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -20,7 +20,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -36,7 +36,7 @@ namespace ESM case ESM::FourCC<'N','P','C','S'>::value: mPowers.add(esm); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 703a4f4cb..8455daeac 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -71,14 +71,14 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mName = esm.getHString(); break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mData, 12); hasData = true; break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index 2ad14b9f2..61960b87d 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -49,7 +49,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -65,7 +65,7 @@ namespace ESM case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 8a88e6d7a..2ef69e5e9 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -21,7 +21,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -47,7 +47,7 @@ namespace ESM case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 372683750..739b0d7db 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -38,7 +38,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -66,7 +66,7 @@ namespace ESM case ESM::FourCC<'N','P','C','O'>::value: mInventory.add(esm); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index e593ff540..0b53e5737 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -30,7 +30,7 @@ namespace ESM { esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -79,7 +79,7 @@ namespace ESM { case AI_CNDT: mAiPackage.add(esm); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index 30fa3cfef..bc87c4f57 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -44,7 +44,7 @@ namespace ESM } break; } - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); mType = Unknown; isDeleted = true; diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index 706e938e8..6d8c0978c 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -18,7 +18,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -37,7 +37,7 @@ namespace ESM case ESM::FourCC<'A','N','A','M'>::value: mCloseSound = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 5580ef222..ae83c63f7 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -20,7 +20,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -31,7 +31,7 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEffects.add(esm); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index f550a5538..75c482d20 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -42,7 +42,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -68,7 +68,7 @@ namespace ESM mReactions[faction] = reaction; break; } - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index d1f20a3d4..246baf026 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -67,7 +67,7 @@ namespace ESM case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mResponse = esm.getHString(); break; case ESM::FourCC<'S','C','V','R'>::value: @@ -93,7 +93,7 @@ namespace ESM mQuestStatus = QS_Restart; esm.skipRecord(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index a481d5b79..e00e73ab0 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -39,7 +39,7 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 13f65ea43..87ec2af55 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -105,7 +105,7 @@ namespace ESM case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mFlags); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index 6245ec856..8c0d50324 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -17,7 +17,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -49,7 +49,7 @@ namespace ESM hasList = true; break; } - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index a0fedc3ad..20c700b23 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -42,7 +42,7 @@ namespace ESM case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index be93eaa0e..fc6af8699 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -39,7 +39,7 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index e9cd4578d..a42ae7c5b 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -30,7 +30,7 @@ namespace ESM case ESM::FourCC<'D','A','T','A'>::value: mTexture = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index f03cb9a85..199b4e3b2 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -39,7 +39,7 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 72333add3..e524e6a7c 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -28,7 +28,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -98,7 +98,7 @@ namespace ESM case AI_CNDT: mAiPackage.add(esm); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 95e6a906f..69ee60eeb 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -48,7 +48,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mCell = esm.getHString(); break; case ESM::FourCC<'D','A','T','A'>::value: @@ -111,7 +111,7 @@ namespace ESM } break; } - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index 31caeff41..baf466490 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -39,7 +39,7 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index d5172a133..a37175144 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -31,7 +31,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -48,7 +48,7 @@ namespace ESM case ESM::FourCC<'N','P','C','S'>::value: mPowers.add(esm); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 9f3089763..e5382f50b 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -18,7 +18,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -65,7 +65,7 @@ namespace ESM esm.getHT(sr, 33); mSoundList.push_back(sr); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index 00d2ebf08..e4af3d937 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -39,7 +39,7 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index b36cf7ecf..af716cab6 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -91,7 +91,7 @@ namespace ESM case ESM::FourCC<'S','C','T','X'>::value: mScriptText = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index 189cc97df..d84fe624d 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -33,7 +33,7 @@ namespace ESM case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index d3a82e198..82f169e54 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -30,7 +30,7 @@ namespace ESM esm.getHT(mData, 3); hasData = true; break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 16ffb63ff..728b7bc2a 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -21,7 +21,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -37,7 +37,7 @@ namespace ESM esm.getHT(s, 24); mEffects.mList.push_back(s); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index a211a99bf..ab4c09750 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -27,7 +27,7 @@ namespace ESM mData = esm.getHString(); hasData = true; break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index eee7a50f5..62d495ee3 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -18,14 +18,14 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index b0bc1dad6..880a26bcb 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -19,7 +19,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().val) { - case ESM::FourCC<'N','A','M','E'>::value: + case ESM::SREC_NAME: mId = esm.getHString(); hasName = true; break; @@ -42,7 +42,7 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEnchant = esm.getHString(); break; - case ESM::FourCC<'D','E','L','E'>::value: + case ESM::SREC_DELE: esm.skipHSub(); isDeleted = true; break; From 25bb8a59ef9228684aacfd39027176b987529a82 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Nov 2015 20:50:57 +0100 Subject: [PATCH 309/441] esmtool fix (cherry picked from commit 9116c701d518f791252fe2c991d24938bf03e3ea) Conflicts: apps/esmtool/record.cpp --- apps/esmtool/record.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 077927c99..d7cbea73b 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -867,6 +867,7 @@ void Record::print() std::cout << " Unknown1: " << data->mUnk1 << std::endl; std::cout << " Unknown2: " << data->mUnk2 << std::endl; } + mData.unloadData(); std::cout << " Deleted: " << mIsDeleted << std::endl; } From f9e12d6fbd811433f032dfb1e62bb942091bdd56 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Nov 2015 20:27:06 +0100 Subject: [PATCH 310/441] Delete empty test (cherry picked from commit d0d8c2ededfcb45d8c5aebaac23bf076f719b4e3) --- apps/openmw_test_suite/CMakeLists.txt | 3 +-- .../components/misc/test_stringops.cpp | 14 -------------- 2 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 apps/openmw_test_suite/components/misc/test_stringops.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 2ffb7ffa0..a27c8f1a4 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -4,8 +4,7 @@ if (GTEST_FOUND) include_directories(${GTEST_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES - components/misc/test_*.cpp - mwdialogue/test_*.cpp + mwdialogue/test_keywordsearch.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/components/misc/test_stringops.cpp b/apps/openmw_test_suite/components/misc/test_stringops.cpp deleted file mode 100644 index 55fe0e0c2..000000000 --- a/apps/openmw_test_suite/components/misc/test_stringops.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include "components/misc/stringops.hpp" - -struct StringOpsTest : public ::testing::Test -{ - protected: - virtual void SetUp() - { - } - - virtual void TearDown() - { - } -}; From c221be76dc5d9e24b16c6201b0d058d378c1993b Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Nov 2015 23:12:07 +0100 Subject: [PATCH 311/441] Tests: add dialogue_merging_test (requires some data files) (cherry picked from commit 38c155c579342f8748e3c93f531be1f1da91784a) --- apps/openmw_test_suite/CMakeLists.txt | 4 + apps/openmw_test_suite/mwworld/test_store.cpp | 172 ++++++++++++++++++ .../loadinglistener/loadinglistener.hpp | 14 +- 3 files changed, 183 insertions(+), 7 deletions(-) create mode 100644 apps/openmw_test_suite/mwworld/test_store.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index a27c8f1a4..2300f97a3 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -4,6 +4,10 @@ if (GTEST_FOUND) include_directories(${GTEST_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES + ../openmw/mwworld/store.cpp + ../openmw/mwworld/esmstore.cpp + mwworld/test_store.cpp + mwdialogue/test_keywordsearch.cpp ) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp new file mode 100644 index 000000000..64e064865 --- /dev/null +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -0,0 +1,172 @@ +#include + +#include + +#include +#include +#include + +#include "apps/openmw/mwworld/esmstore.hpp" + +/// Base class for tests of ESMStore that rely on external content files to produce the test data +struct ContentFileTest : public ::testing::Test +{ + protected: + + virtual void SetUp() + { + readContentFiles(); + + // load the content files + std::vector readerList; + readerList.resize(mContentFiles.size()); + + int index=0; + for (std::vector::const_iterator it = mContentFiles.begin(); it != mContentFiles.end(); ++it) + { + ESM::ESMReader lEsm; + lEsm.setEncoder(NULL); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&readerList); + lEsm.open(it->string()); + readerList[index] = lEsm; + std::auto_ptr listener; + listener.reset(new Loading::Listener); + mEsmStore.load(readerList[index], listener.get()); + + ++index; + } + + mEsmStore.setUp(); + } + + virtual void TearDown() + { + } + + // read absolute path to content files from openmw.cfg + void readContentFiles() + { + boost::program_options::variables_map variables; + + boost::program_options::options_description desc("Allowed options"); + desc.add_options() + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) + ("content", boost::program_options::value >()->default_value(std::vector(), "") + ->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") + ("data-local", boost::program_options::value()->default_value("")); + + boost::program_options::notify(variables); + + mConfigurationManager.readConfiguration(variables, desc, true); + + Files::PathContainer dataDirs, dataLocal; + if (!variables["data"].empty()) { + dataDirs = Files::PathContainer(variables["data"].as()); + } + + std::string local = variables["data-local"].as(); + if (!local.empty()) { + dataLocal.push_back(Files::PathContainer::value_type(local)); + } + + mConfigurationManager.processPaths (dataDirs); + mConfigurationManager.processPaths (dataLocal, true); + + if (!dataLocal.empty()) + dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); + + Files::Collections collections (dataDirs, true); + + std::vector contentFiles = variables["content"].as >(); + for (std::vector::iterator it = contentFiles.begin(); it != contentFiles.end(); ++it) + mContentFiles.push_back(collections.getPath(*it)); + } + +protected: + Files::ConfigurationManager mConfigurationManager; + MWWorld::ESMStore mEsmStore; + std::vector mContentFiles; +}; + +/// Print results of the dialogue merging process, i.e. the resulting linked list. +TEST_F(ContentFileTest, dialogue_merging_test) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + const std::string file = "test_dialogue_merging.txt"; + + boost::filesystem::ofstream stream; + stream.open(file); + + const MWWorld::Store& dialStore = mEsmStore.get(); + for (MWWorld::Store::iterator it = dialStore.begin(); it != dialStore.end(); ++it) + { + const ESM::Dialogue& dial = *it; + stream << "Dialogue: " << dial.mId << std::endl; + + for (ESM::Dialogue::InfoContainer::const_iterator infoIt = dial.mInfo.begin(); infoIt != dial.mInfo.end(); ++infoIt) + { + const ESM::DialInfo& info = *infoIt; + stream << info.mId << std::endl; + } + stream << std::endl; + } + + std::cout << "dialogue_merging_test successful, results printed to " << file << std::endl; +} + +/* +TEST_F(ContentFileTest, diagnostics) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + +} +*/ + +// TODO: +/// Print results of autocalculated NPC spell lists. Also serves as test for attribute/skill autocalculation which the spell autocalculation heavily relies on +/// - even rounding errors can completely change the resulting spell lists. +/* +TEST_F(ContentFileTest, autocalc_test) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + +} +*/ + +/* +/// Base class for tests of ESMStore that do not rely on external content files +struct StoreTest : public ::testing::Test +{ +protected: + MWWorld::ESMStore mEsmStore; +}; + +/// Tests deletion of records. +TEST_F(StoreTest, delete_test) +{ + + +} + +/// Tests overwriting of records. +TEST_F(StoreTest, overwrite_test) +{ + +} +*/ diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 47962015c..558111b34 100644 --- a/components/loadinglistener/loadinglistener.hpp +++ b/components/loadinglistener/loadinglistener.hpp @@ -6,18 +6,18 @@ namespace Loading class Listener { public: - virtual void setLabel (const std::string& label) = 0; + virtual void setLabel (const std::string& label) {} // Use ScopedLoad instead of using these directly - virtual void loadingOn() = 0; - virtual void loadingOff() = 0; + virtual void loadingOn() {} + virtual void loadingOff() {} /// Indicate that some progress has been made, without specifying how much - virtual void indicateProgress () = 0; + virtual void indicateProgress () {} - virtual void setProgressRange (size_t range) = 0; - virtual void setProgress (size_t value) = 0; - virtual void increaseProgress (size_t increase = 1) = 0; + virtual void setProgressRange (size_t range) {} + virtual void setProgress (size_t value) {} + virtual void increaseProgress (size_t increase = 1) {} }; // Used for stopping a loading sequence when the object goes out of scope From c3031da20e70dafb6ef5c2dd11858245325c13f0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Nov 2015 23:38:38 +0100 Subject: [PATCH 312/441] Tests: add content_diagnostics_test (requires some data files) (cherry picked from commit 771193bae8d00e0cee089544973deeffafdaccc8) --- apps/openmw_test_suite/mwworld/test_store.cpp | 65 ++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 64e064865..bb8bcc37c 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -120,8 +120,61 @@ TEST_F(ContentFileTest, dialogue_merging_test) std::cout << "dialogue_merging_test successful, results printed to " << file << std::endl; } -/* -TEST_F(ContentFileTest, diagnostics) +// Note: here we don't test records that don't use string names (e.g. Land, Pathgrid, Cell) +#define RUN_TEST_FOR_TYPES(func, arg1, arg2) \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); + +template +void printRecords(MWWorld::ESMStore& esmStore, std::ostream& outStream) +{ + const MWWorld::Store& store = esmStore.get(); + outStream << store.getSize() << " " << T::getRecordType() << " records" << std::endl; + + for (typename MWWorld::Store::iterator it = store.begin(); it != store.end(); ++it) + { + const T& record = *it; + outStream << record.mId << std::endl; + } + + outStream << std::endl; +} + +/// Print some basic diagnostics about the loaded content files, e.g. number of records and names of those records +/// Also used to test the iteration order of records +TEST_F(ContentFileTest, content_diagnostics_test) { if (mContentFiles.empty()) { @@ -129,9 +182,15 @@ TEST_F(ContentFileTest, diagnostics) return; } + const std::string file = "test_content_diagnostics.txt"; + + boost::filesystem::ofstream stream; + stream.open(file); + + RUN_TEST_FOR_TYPES(printRecords, mEsmStore, stream); + std::cout << "diagnostics_test successful, results printed to " << file << std::endl; } -*/ // TODO: /// Print results of autocalculated NPC spell lists. Also serves as test for attribute/skill autocalculation which the spell autocalculation heavily relies on From d3ada38f018c1e6bfa0100e207b48f35572fdc7f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Nov 2015 00:12:59 +0100 Subject: [PATCH 313/441] Tests: add record deletion test (cherry picked from commit 1e817a976fb836600c74cd4dbfe94742639f1179) --- apps/openmw_test_suite/mwworld/test_store.cpp | 65 +++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index bb8bcc37c..dfa1c0b25 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -4,10 +4,14 @@ #include #include +#include +#include #include #include "apps/openmw/mwworld/esmstore.hpp" +static Loading::Listener dummyListener; + /// Base class for tests of ESMStore that rely on external content files to produce the test data struct ContentFileTest : public ::testing::Test { @@ -30,9 +34,7 @@ struct ContentFileTest : public ::testing::Test lEsm.setGlobalReaderList(&readerList); lEsm.open(it->string()); readerList[index] = lEsm; - std::auto_ptr listener; - listener.reset(new Loading::Listener); - mEsmStore.load(readerList[index], listener.get()); + mEsmStore.load(readerList[index], &dummyListener); ++index; } @@ -208,7 +210,6 @@ TEST_F(ContentFileTest, autocalc_test) } */ -/* /// Base class for tests of ESMStore that do not rely on external content files struct StoreTest : public ::testing::Test { @@ -216,11 +217,66 @@ protected: MWWorld::ESMStore mEsmStore; }; + +/// Create an ESM file in-memory containing the specified record. +/// @param deleted Write record with deleted flag? +template +Files::IStreamPtr getEsmFile(T record, bool deleted) +{ + ESM::ESMWriter writer; + std::stringstream* stream = new std::stringstream; + writer.setFormat(0); + writer.save(*stream); + writer.startRecord(T::sRecordId); + writer.writeHNString("NAME", record.mId); + if (deleted) + writer.writeHNT("DELE", (int)1); + record.save(writer); + writer.endRecord(T::sRecordId); + + return Files::IStreamPtr(stream); +} + /// Tests deletion of records. TEST_F(StoreTest, delete_test) { + const std::string recordId = "foobar"; + + typedef ESM::Apparatus RecordType; + RecordType record; + record.blank(); + record.mId = recordId; + ESM::ESMReader reader; + std::vector readerList; + readerList.push_back(reader); + reader.setGlobalReaderList(&readerList); + + // master file inserts a record + Files::IStreamPtr file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 1); + + // now a plugin deletes it + file = getEsmFile(record, true); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 0); + + // now another plugin inserts it again + // expected behaviour is the record to reappear rather than staying deleted + file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 1); } /// Tests overwriting of records. @@ -228,4 +284,3 @@ TEST_F(StoreTest, overwrite_test) { } -*/ From 0d9ffe8119a80454441ee08a713dec36ba7411d1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Nov 2015 00:17:13 +0100 Subject: [PATCH 314/441] Tests: add record overwrite test (cherry picked from commit f91aae2350a1c6ce188b4f53b5714eb7a288306d) --- apps/openmw_test_suite/mwworld/test_store.cpp | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index dfa1c0b25..993386207 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -196,7 +196,7 @@ TEST_F(ContentFileTest, content_diagnostics_test) // TODO: /// Print results of autocalculated NPC spell lists. Also serves as test for attribute/skill autocalculation which the spell autocalculation heavily relies on -/// - even rounding errors can completely change the resulting spell lists. +/// - even incorrect rounding modes can completely change the resulting spell lists. /* TEST_F(ContentFileTest, autocalc_test) { @@ -282,5 +282,38 @@ TEST_F(StoreTest, delete_test) /// Tests overwriting of records. TEST_F(StoreTest, overwrite_test) { + const std::string recordId = "foobar"; + const std::string recordIdUpper = "Foobar"; + + typedef ESM::Apparatus RecordType; + + RecordType record; + record.blank(); + record.mId = recordId; + + ESM::ESMReader reader; + std::vector readerList; + readerList.push_back(reader); + reader.setGlobalReaderList(&readerList); + + // master file inserts a record + Files::IStreamPtr file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + // now a plugin overwrites it with changed data + record.mId = recordIdUpper; // change id to uppercase, to test case smashing while we're at it + record.mModel = "the_new_model"; + file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + // verify that changes were actually applied + const RecordType* overwrittenRec = mEsmStore.get().search(recordId); + + ASSERT_TRUE (overwrittenRec != NULL); + ASSERT_TRUE (overwrittenRec && overwrittenRec->mModel == "the_new_model"); } From 33c0db1d9aee7768b6a2ac7c5b38ea89ab3aa458 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Nov 2015 00:23:35 +0100 Subject: [PATCH 315/441] Adjust tests to work with esm_rewrite branch. (cherry picked from commit aae1aa3708bf7a48e768f6b323372f5a61aa93ca) --- apps/openmw_test_suite/mwworld/test_store.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 993386207..ac21470de 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -5,14 +5,13 @@ #include #include #include -#include #include #include "apps/openmw/mwworld/esmstore.hpp" static Loading::Listener dummyListener; -/// Base class for tests of ESMStore that rely on external content files to produce the test data +/// Base class for tests of ESMStore that rely on external content files to produce the test results struct ContentFileTest : public ::testing::Test { protected: @@ -228,10 +227,7 @@ Files::IStreamPtr getEsmFile(T record, bool deleted) writer.setFormat(0); writer.save(*stream); writer.startRecord(T::sRecordId); - writer.writeHNString("NAME", record.mId); - if (deleted) - writer.writeHNT("DELE", (int)1); - record.save(writer); + record.save(writer, deleted); writer.endRecord(T::sRecordId); return Files::IStreamPtr(stream); From 1334091613dab1c6ae59c07de264f20b5f7762a3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 5 Dec 2015 17:28:32 +1100 Subject: [PATCH 316/441] Resolve merge issues. --- apps/opencs/model/doc/savingstages.cpp | 25 ++++---- apps/opencs/model/world/land.cpp | 2 +- apps/opencs/model/world/land.hpp | 1 - apps/opencs/model/world/landtexture.cpp | 5 +- components/esm/loadland.cpp | 85 ++++++++++++++++++++++--- components/esmterrain/storage.cpp | 73 ++++++++++++++------- 6 files changed, 140 insertions(+), 51 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 546e8471c..16a91c865 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -127,27 +127,22 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message if (topic.isModified() || infoModified) { - ESM::Dialogue dialogue = topic.get(); - if (infoModified && state != CSMWorld::RecordBase::State_Modified - && state != CSMWorld::RecordBase::State_ModifiedOnly) + if (infoModified && topic.mState != CSMWorld::RecordBase::State_Modified + && topic.mState != CSMWorld::RecordBase::State_ModifiedOnly) { mState.getWriter().startRecord (topic.mBase.sRecordId); mState.getWriter().writeHNCString ("NAME", topic.mBase.mId); - topic.mBase.save (mState.getWriter()); + topic.mBase.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); mState.getWriter().endRecord (topic.mBase.sRecordId); } else { mState.getWriter().startRecord (topic.mModified.sRecordId); mState.getWriter().writeHNCString ("NAME", topic.mModified.mId); - topic.mModified.save (mState.getWriter()); + topic.mModified.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); mState.getWriter().endRecord (topic.mModified.sRecordId); } - writer.startRecord (dialogue.sRecordId); - dialogue.save (writer); - writer.endRecord (dialogue.sRecordId); - // write modified selected info records for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { @@ -398,10 +393,14 @@ void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) if (land.isModified() || land.mState == CSMWorld::RecordBase::State_Deleted) { - const CSMWorld::Land& record = land.get(); - writer.startRecord (record.mLand->sRecordId); - record.mLand->save (writer, land.mState == CSMWorld::RecordBase::State_Deleted); - writer.endRecord (record.mLand->sRecordId); + CSMWorld::Land record = land.get(); + writer.startRecord (record.sRecordId); + record.save (writer, land.mState == CSMWorld::RecordBase::State_Deleted); + + if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes)) + data->save (mState.getWriter()); + + writer.endRecord (record.sRecordId); } } diff --git a/apps/opencs/model/world/land.cpp b/apps/opencs/model/world/land.cpp index e134eed38..80f86c746 100644 --- a/apps/opencs/model/world/land.cpp +++ b/apps/opencs/model/world/land.cpp @@ -6,7 +6,7 @@ namespace CSMWorld { void Land::load(ESM::ESMReader &esm, bool &isDeleted) { - mLand->load(esm, isDeleted); + ESM::Land::load(esm, isDeleted); std::ostringstream stream; stream << "#" << mX << " " << mY; diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index fd97ac2c5..e5f25c1d3 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -10,7 +10,6 @@ namespace CSMWorld /// \brief Wrapper for Land record. Encodes X and Y cell index in the ID. /// /// \todo Add worldspace support to the Land record. - /// \todo Add a proper copy constructor (currently worked around using shared_ptr) struct Land : public ESM::Land { std::string mId; diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index 9e8dcff9f..266377d0f 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -7,11 +7,8 @@ namespace CSMWorld void LandTexture::load(ESM::ESMReader &esm, bool &isDeleted) { ESM::LandTexture::load(esm, isDeleted); - int plugin = esm.getIndex(); - std::ostringstream stream; - stream << mIndex << "_" << plugin; - mId = stream.str(); + mPluginIndex = esm.getIndex(); } } diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 87ec2af55..e7be03321 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -55,16 +55,6 @@ namespace ESM } } - void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) - { - int readPos = 0; //bit ugly, but it works - for ( int y1 = 0; y1 < 4; y1++ ) - for ( int x1 = 0; x1 < 4; x1++ ) - for ( int y2 = 0; y2 < 4; y2++) - for ( int x2 = 0; x2 < 4; x2++ ) - out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++]; - } - Land::Land() : mFlags(0) , mX(0) @@ -77,6 +67,16 @@ namespace ESM { } + void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) + { + int readPos = 0; //bit ugly, but it works + for ( int y1 = 0; y1 < 4; y1++ ) + for ( int x1 = 0; x1 < 4; x1++ ) + for ( int y2 = 0; y2 < 4; y2++) + for ( int x2 = 0; x2 < 4; x2++ ) + out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++]; + } + Land::~Land() { delete mLandData; @@ -256,4 +256,69 @@ namespace ESM return (mDataLoaded & flags) == (flags & mDataTypes); } + Land::Land (const Land& land) + : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), + mEsm (land.mEsm), mContext (land.mContext), mDataTypes (land.mDataTypes), + mDataLoaded (land.mDataLoaded), + mLandData (land.mLandData ? new LandData (*land.mLandData) : 0) + {} + + Land& Land::operator= (Land land) + { + swap (land); + return *this; + } + + void Land::swap (Land& land) + { + std::swap (mFlags, land.mFlags); + std::swap (mX, land.mX); + std::swap (mY, land.mY); + std::swap (mPlugin, land.mPlugin); + std::swap (mEsm, land.mEsm); + std::swap (mContext, land.mContext); + std::swap (mDataTypes, land.mDataTypes); + std::swap (mDataLoaded, land.mDataLoaded); + std::swap (mLandData, land.mLandData); + } + + const Land::LandData *Land::getLandData (int flags) const + { + if (!(flags & mDataTypes)) + return 0; + + loadData (flags); + return mLandData; + } + + const Land::LandData *Land::getLandData() const + { + return mLandData; + } + + Land::LandData *Land::getLandData() + { + return mLandData; + } + + void Land::add (int flags) + { + if (!mLandData) + mLandData = new LandData; + + mDataTypes |= flags; + mDataLoaded |= flags; + } + + void Land::remove (int flags) + { + mDataTypes &= ~flags; + mDataLoaded &= ~flags; + + if (!mDataLoaded) + { + delete mLandData; + mLandData = 0; + } + } } diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 83fa85890..26ef890bd 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -1,5 +1,8 @@ #include "storage.hpp" +#include +#include + #include #include #include @@ -32,19 +35,22 @@ namespace ESMTerrain Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); + int cellX = static_cast(std::floor(origin.x)); + int cellY = static_cast(std::floor(origin.y)); + + int startRow = (origin.x - cellX) * ESM::Land::LAND_SIZE; + int startColumn = (origin.y - cellY) * ESM::Land::LAND_SIZE; - int cellX = static_cast(origin.x); - int cellY = static_cast(origin.y); + int endRow = startRow + size * (ESM::Land::LAND_SIZE-1) + 1; + int endColumn = startColumn + size * (ESM::Land::LAND_SIZE-1) + 1; if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VHGT)) { min = std::numeric_limits::max(); max = -std::numeric_limits::max(); - for (int row=0; rowmHeights[col*ESM::Land::LAND_SIZE+row]; if (h > max) @@ -141,17 +147,15 @@ namespace ESMTerrain size_t increment = 1 << lodLevel; Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); - int startX = static_cast(origin.x); - int startY = static_cast(origin.y); + int startCellX = static_cast(std::floor(origin.x)); + int startCellY = static_cast(std::floor(origin.y)); size_t numVerts = static_cast(size*(ESM::Land::LAND_SIZE - 1) / increment + 1); - colours.resize(numVerts*numVerts*4); positions.resize(numVerts*numVerts*3); normals.resize(numVerts*numVerts*3); + colours.resize(numVerts*numVerts*4); Ogre::Vector3 normal; Ogre::ColourValue color; @@ -160,10 +164,10 @@ namespace ESMTerrain float vertX = 0; float vertY_ = 0; // of current cell corner - for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) + for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) { float vertX_ = 0; // of current cell corner - for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) + for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT); const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML); @@ -173,20 +177,33 @@ namespace ESMTerrain int colStart = 0; // Skip the first row / column unless we're at a chunk edge, // since this row / column is already contained in a previous cell + // This is only relevant if we're creating a chunk spanning multiple cells if (colStart == 0 && vertY_ != 0) colStart += increment; if (rowStart == 0 && vertX_ != 0) rowStart += increment; + // Only relevant for chunks smaller than (contained in) one cell + rowStart += (origin.x - startCellX) * ESM::Land::LAND_SIZE; + colStart += (origin.y - startCellY) * ESM::Land::LAND_SIZE; + int rowEnd = rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; + int colEnd = colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; + vertY = vertY_; - for (int col=colStart; col(vertX*numVerts * 3 + vertY * 3)] = ((vertX / float(numVerts - 1) - 0.5f) * size * 8192); positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 1)] = ((vertY / float(numVerts - 1) - 0.5f) * size * 8192); + assert(row >= 0 && row < ESM::Land::LAND_SIZE); + assert(col >= 0 && col < ESM::Land::LAND_SIZE); + + assert (vertX < numVerts); + assert (vertY < numVerts); + float height = -2048; if (heightData) height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; @@ -290,7 +307,7 @@ namespace ESMTerrain // NB: All vtex ids are +1 compared to the ltex ids const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); - //TODO this is needed due to MWs messed up texture handling + // this is needed due to MWs messed up texture handling std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture); return texture; @@ -320,8 +337,19 @@ namespace ESMTerrain // and interpolate the rest of the cell by hand? :/ Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); - int cellX = static_cast(origin.x); - int cellY = static_cast(origin.y); + int cellX = static_cast(std::floor(origin.x)); + int cellY = static_cast(std::floor(origin.y)); + + int realTextureSize = ESM::Land::LAND_TEXTURE_SIZE+1; // add 1 to wrap around next cell + + int rowStart = (origin.x - cellX) * realTextureSize; + int colStart = (origin.y - cellY) * realTextureSize; + int rowEnd = rowStart + chunkSize * (realTextureSize-1) + 1; + int colEnd = colStart + chunkSize * (realTextureSize-1) + 1; + + assert (rowStart >= 0 && colStart >= 0); + assert (rowEnd <= realTextureSize); + assert (colEnd <= realTextureSize); // Save the used texture indices so we know the total number of textures // and number of required blend maps @@ -332,8 +360,8 @@ namespace ESMTerrain // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. textureIndices.insert(std::make_pair(0,0)); - for (int y=0; ysecond; int blendIndex = (pack ? static_cast(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; From 940e982d65150bec48c4384af5c673025e327108 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 14 Nov 2015 16:02:42 +0100 Subject: [PATCH 317/441] Add FFMPEG to include path for OpenMW I'm a bit confused; `mwsound/ffmpeg_decoder.hpp/cpp` requires FFMPEG headers to compile, how did this work in the first place? (cherry picked from commit d2a417580446deec3c465875ae9f85a2d455be1d) --- apps/openmw/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index d54152100..53c9daf76 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -108,7 +108,10 @@ endif () # Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING # when we change the backend. -include_directories(${SOUND_INPUT_INCLUDES}) +include_directories( + ${SOUND_INPUT_INCLUDES} + ${FFMPEG_INCLUDE_DIRS} +) target_link_libraries(openmw ${OENGINE_LIBRARY} From d3caeaf898518b494fdce767195be5809679d426 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Nov 2015 17:17:22 +0100 Subject: [PATCH 318/441] Remove unused SOUND_INPUT_INCLUDES cmake variable. (cherry picked from commit 0220e82259bd799e93eb6ae5f2288ba4c71fcd3e) Conflicts: apps/openmw/CMakeLists.txt --- CMakeLists.txt | 2 -- apps/openmw/CMakeLists.txt | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb1ac83cc..bd01db606 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,8 +119,6 @@ else() endif() endif() -set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES}) - # TinyXML option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) if(USE_SYSTEM_TINYXML) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 53c9daf76..ffa52042c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -109,7 +109,6 @@ endif () # Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING # when we change the backend. include_directories( - ${SOUND_INPUT_INCLUDES} ${FFMPEG_INCLUDE_DIRS} ) @@ -126,6 +125,9 @@ target_link_libraries(openmw ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_WAVE_LIBRARY} + ${OPENAL_LIBRARY} + ${FFMPEG_LIBRARIES} + ${BULLET_LIBRARIES} ${MYGUI_LIBRARIES} ${SDL2_LIBRARY} ${MYGUI_PLATFORM_LIBRARIES} From ebf9ffd1da558beb5410963497e5587abaa8c719 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Nov 2015 16:04:52 +0100 Subject: [PATCH 319/441] Fix double writing of Dialogue NAME in OpenCS (cherry picked from commit af4923577b4f97ef4f5c6e89c58bf4badf3228ec) --- apps/opencs/model/doc/savingstages.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 16a91c865..db38c4779 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -131,14 +131,12 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message && topic.mState != CSMWorld::RecordBase::State_ModifiedOnly) { mState.getWriter().startRecord (topic.mBase.sRecordId); - mState.getWriter().writeHNCString ("NAME", topic.mBase.mId); topic.mBase.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); mState.getWriter().endRecord (topic.mBase.sRecordId); } else { mState.getWriter().startRecord (topic.mModified.sRecordId); - mState.getWriter().writeHNCString ("NAME", topic.mModified.mId); topic.mModified.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); mState.getWriter().endRecord (topic.mModified.sRecordId); } From 0eca29eb6232a7ce2dc21754780a52ee14945e1c Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 18 Nov 2015 16:47:03 +0100 Subject: [PATCH 320/441] Ignore Creature INDX subrecords Found in some .ess files, not sure what they mean. (cherry picked from commit 0bdfd1b0d7c625eb8cd82bb402f3c6c9a528515b) --- components/esm/loadcrea.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 0b53e5737..b51782089 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -1,5 +1,7 @@ #include "loadcrea.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -83,6 +85,12 @@ namespace ESM { esm.skipHSub(); isDeleted = true; break; + case ESM::FourCC<'I','N','D','X'>::value: + // seems to occur only in .ESS files, unsure of purpose + int index; + esm.getHT(index); + std::cerr << "Creature::load: Unhandled INDX " << index << std::endl; + break; default: esm.fail("Unknown subrecord"); break; From 30b28bfd07b4ba54c95745bdd9bae8f8fafed127 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 21 Nov 2015 11:56:24 +0100 Subject: [PATCH 321/441] adjusted startup warning message for recent improvements regarding loading/saving (cherry picked from commit e0e9e7f8c2e2e666f125c2c3a8b1c098ec4ea43a) --- apps/opencs/view/doc/startup.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index a9d697f1c..67ff50dab 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -104,14 +104,16 @@ CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) layout->addWidget (createButtons()); layout->addWidget (createTools()); - /// \todo remove this label once loading and saving are fully implemented - QLabel *warning = new QLabel ("WARNING:

    OpenCS is in alpha stage.
    The code for loading and saving is incomplete.
    This version of OpenCS is only a preview.
    Do NOT use it for real editing!
    You will lose records both on loading and on saving.

    Please note:
    If you lose data and come to the OpenMW forum to complain,
    we will mock you.
    "); + /// \todo remove this label once we are feature complete and convinced that this thing is + /// working properly. + QLabel *warning = new QLabel ("WARNING: OpenMW-CS is in alpha stage.

    The editor is not feature complete and not sufficiently tested.
    In theory your data should be safe. But we strongly advice to make backups regularly if you are working with live data.
    "); QFont font; font.setPointSize (12); font.setBold (true); warning->setFont (font); + warning->setWordWrap (true); layout->addWidget (warning, 1); From 0366a056497168d35b0193d62713aaea78c57eab Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 21 Nov 2015 12:14:57 +0100 Subject: [PATCH 322/441] Removed validator for filenames in OpenMW-CS (Fixes #2918) (cherry picked from commit b74b274ac0e88ad7c833b908bb301dee3986264f) --- apps/opencs/view/doc/filewidget.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index 110d561c1..9e9acdfbe 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -16,7 +16,6 @@ CSVDoc::FileWidget::FileWidget (QWidget *parent) : QWidget (parent), mAddon (fal QHBoxLayout *layout = new QHBoxLayout (this); mInput = new QLineEdit (this); - mInput->setValidator (new QRegExpValidator(QRegExp("^[a-zA-Z0-9_-\\s]*$"))); layout->addWidget (mInput, 1); From 2c5f0bf6c5938fdb33d61e9c4f6b75cd645bccf9 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 21 Nov 2015 12:45:11 +0100 Subject: [PATCH 323/441] hide script error list when there are no errors (Fixes #2867) (cherry picked from commit 1093a53cf9f2a85374e145d96bb21bfb46eb13ad) --- apps/opencs/view/world/scriptsubview.cpp | 19 +++++++++++++++++++ apps/opencs/view/world/scriptsubview.hpp | 2 ++ 2 files changed, 21 insertions(+) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index eb0c70656..7907dca03 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -54,6 +54,7 @@ void CSVWorld::ScriptSubView::updateDeletedState() if (isDeleted()) { mErrors->clear(); + adjustSplitter(); mEditor->setEnabled (false); } else @@ -63,6 +64,22 @@ void CSVWorld::ScriptSubView::updateDeletedState() } } +void CSVWorld::ScriptSubView::adjustSplitter() +{ + QList sizes; + + if (mErrors->rowCount()) + { + sizes << 1 << 1; + } + else + { + sizes << 1 << 0; + } + + mMain->setSizes (sizes); +} + CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) @@ -347,4 +364,6 @@ void CSVWorld::ScriptSubView::updateRequest() QString source = mModel->data (index).toString(); mErrors->update (source.toUtf8().constData()); + + adjustSplitter(); } diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 907dc7958..c04a7df7c 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -58,6 +58,8 @@ namespace CSVWorld void updateDeletedState(); + void adjustSplitter(); + public: ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); From 8da783d877bec4939a5e6baa00d7e4998dab4928 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 21 Nov 2015 12:52:32 +0100 Subject: [PATCH 324/441] do not adjust error panel height if panal was already open (cherry picked from commit 26640d17eb053176b45482e801236415a42ddf1f) --- apps/opencs/view/world/scriptsubview.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 7907dca03..0faf61f9b 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -70,6 +70,9 @@ void CSVWorld::ScriptSubView::adjustSplitter() if (mErrors->rowCount()) { + if (mErrors->height()) + return; // keep old height if the error panel was already open + sizes << 1 << 1; } else From 8c4d414635c1192c9aab49ae3685c2cc12390d91 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 21 Nov 2015 14:19:14 +0100 Subject: [PATCH 325/441] remember script error panel height per scriptsubview (cherry picked from commit f5c61ee616bfaed911a574c6d43aa6842c224773) --- apps/opencs/view/world/scriptsubview.cpp | 12 ++++++++++-- apps/opencs/view/world/scriptsubview.hpp | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 0faf61f9b..c45e45f56 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -73,10 +73,13 @@ void CSVWorld::ScriptSubView::adjustSplitter() if (mErrors->height()) return; // keep old height if the error panel was already open - sizes << 1 << 1; + sizes << (mMain->height()-mErrorHeight-mMain->handleWidth()) << mErrorHeight; } else { + if (mErrors->height()) + mErrorHeight = mErrors->height(); + sizes << 1 << 0; } @@ -85,7 +88,8 @@ void CSVWorld::ScriptSubView::adjustSplitter() CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), - mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) + mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())), + mErrorHeight (100) { std::vector selection (1, id.getId()); mCommandDispatcher.setSelection (selection); @@ -101,6 +105,10 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: mErrors = new ScriptErrorTable (document, this); mMain->addWidget (mErrors); + QList sizes; + sizes << 1 << 0; + mMain->setSizes (sizes); + QWidget *widget = new QWidget (this);; widget->setLayout (&mLayout); setWidget (widget); diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index c04a7df7c..179430ef9 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -47,6 +47,7 @@ namespace CSVWorld QSplitter *mMain; ScriptErrorTable *mErrors; QTimer *mCompileDelay; + int mErrorHeight; private: From ec00bd2c988038eeccaad5f70146ce9553ba91f6 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 21 Nov 2015 14:28:40 +0100 Subject: [PATCH 326/441] make initial size of script error panel configurable (Fixes #2996) (cherry picked from commit 99500f40212c7b37a536c2692d5b066c26c79bbf) --- apps/opencs/model/settings/usersettings.cpp | 5 +++++ apps/opencs/view/world/scriptsubview.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 5cf3bd7a7..87992d0dc 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -366,6 +366,11 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() delay->setRange (0, 10000); delay->setToolTip ("Delay in milliseconds"); + Setting *errorHeight = createSetting (Type_SpinBox, "error-height", + "Initial height of the error panel"); + errorHeight->setDefaultValue (100); + errorHeight->setRange (100, 10000); + Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int"); formatInt->setDefaultValues (QStringList() << "Dark magenta"); formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index c45e45f56..bd66f2bf6 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -89,7 +89,7 @@ void CSVWorld::ScriptSubView::adjustSplitter() CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())), - mErrorHeight (100) + mErrorHeight (CSMSettings::UserSettings::instance().setting ("script-editor/error-height").toInt()) { std::vector selection (1, id.getId()); mCommandDispatcher.setSelection (selection); From a1939ae05fef517cd407ccce35d5b1338c41dca4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 27 Nov 2015 03:22:52 +0100 Subject: [PATCH 327/441] StringUtils: use the locale-unaware tolower function There is no change in behaviour since we were using the C locale. The locale-aware tolower is much slower than the locale-unaware one. At least on Linux/GCC it calls dynamic_cast's, and is overall slower by an order of magnitude. (cherry picked from commit 27e669296e54621472ed5578103ad4306f8a94a9) --- components/misc/stringops.cpp | 8 -------- components/misc/stringops.hpp | 11 ++++------- 2 files changed, 4 insertions(+), 15 deletions(-) delete mode 100644 components/misc/stringops.cpp diff --git a/components/misc/stringops.cpp b/components/misc/stringops.cpp deleted file mode 100644 index 723c1c1e0..000000000 --- a/components/misc/stringops.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "stringops.hpp" - -namespace Misc -{ - -std::locale StringUtils::mLocale = std::locale::classic(); - -} diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 04dedb072..d6bc19069 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -4,18 +4,15 @@ #include #include #include -#include namespace Misc { class StringUtils { - - static std::locale mLocale; struct ci { bool operator()(char x, char y) const { - return std::tolower(x, StringUtils::mLocale) < std::tolower(y, StringUtils::mLocale); + return tolower(x) < tolower(y); } }; @@ -31,7 +28,7 @@ public: std::string::const_iterator xit = x.begin(); std::string::const_iterator yit = y.begin(); for (; xit != x.end(); ++xit, ++yit) { - if (std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) { + if (tolower(*xit) != tolower(*yit)) { return false; } } @@ -45,7 +42,7 @@ public: for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len) { int res = *xit - *yit; - if(res != 0 && std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) + if(res != 0 && tolower(*xit) != tolower(*yit)) return (res > 0) ? 1 : -1; } if(len > 0) @@ -61,7 +58,7 @@ public: /// Transforms input string to lower case w/o copy static std::string &toLower(std::string &inout) { for (unsigned int i=0; i Date: Fri, 27 Nov 2015 21:45:37 +0100 Subject: [PATCH 328/441] Not found Land Textures are no longer a fatal error (Bug #3037) Log warning message and show the default texture when encountering invalid ESM::LandTexture references. (cherry picked from commit 35fa1f5865bcb8370505f0a2b641c8363024c8a7) --- apps/opencs/view/render/terrainstorage.cpp | 4 +--- apps/openmw/mwrender/terrainstorage.cpp | 2 +- components/esmterrain/storage.cpp | 8 +++++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 41fe70c01..11a08faa6 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -36,9 +36,7 @@ namespace CSVRender return ltex; } - std::stringstream error; - error << "Can't find LandTexture " << index << " from plugin " << plugin; - throw std::runtime_error(error.str()); + return NULL; } void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 2808187a1..d2601361d 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -68,7 +68,7 @@ namespace MWRender { const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); - return esmStore.get().find(index, plugin); + return esmStore.get().search(index, plugin); } } diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 26ef890bd..9df4d6890 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -301,11 +301,17 @@ namespace ESMTerrain std::string Storage::getTextureName(UniqueTextureId id) { + static const std::string defaultTexture = "textures\\_land_default.dds"; if (id.first == 0) - return "textures\\_land_default.dds"; // Not sure if the default texture really is hardcoded? + return defaultTexture; // Not sure if the default texture really is hardcoded? // NB: All vtex ids are +1 compared to the ltex ids const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); + if (!ltex) + { + std::cerr << "Unable to find land texture index " << id.first-1 << " in plugin " << id.second << ", using default texture instead" << std::endl; + return defaultTexture; + } // this is needed due to MWs messed up texture handling std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture); From 77394fce99281cea65f30c67dbcd69a2d3eeba88 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 5 Dec 2015 20:38:09 +1100 Subject: [PATCH 329/441] Fix table being sorted twice (at least it appeared that way according to the sample profiler) - Quoting Qt-4.8: "Note: . Setting the property to true with setSortingEnabled() immediately triggers a call to sortByColumn() with the current sort section and order." --- apps/opencs/view/world/table.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 6c4ede6e2..26abf1744 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -280,12 +280,6 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); - setSortingEnabled (sorting); - if (sorting) - { - sortByColumn (mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder); - } - int columns = mModel->columnCount(); for (int i=0; ifindColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder); + } + setSortingEnabled (sorting); + mEditAction = new QAction (tr ("Edit Record"), this); connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord())); addAction (mEditAction); @@ -429,7 +430,7 @@ std::vector CSVWorld::Table::getSelectedIds() const QModelIndexList selectedRows = selectionModel()->selectedRows(); int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - for (QModelIndexList::const_iterator iter (selectedRows.begin()); + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter != selectedRows.end(); ++iter) { From 257126ed69a5f6f964ba771766de061e81f87433 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 5 Dec 2015 20:50:47 +1100 Subject: [PATCH 330/441] Call push_back() if inserting to the end of the vector. It seems MSVC may be generating different code compared to insert(). --- apps/opencs/model/world/collection.hpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 16f5ce51f..a13673461 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -443,19 +443,24 @@ namespace CSMWorld void Collection::insertRecord (const RecordBase& record, int index, UniversalId::Type type) { - if (index<0 || index>static_cast (mRecords.size())) + int size = static_cast(mRecords.size()); + if (index < 0 || index > size) throw std::runtime_error ("index out of range"); const Record& record2 = dynamic_cast&> (record); - mRecords.insert (mRecords.begin()+index, record2); + if (index == size) + mRecords.push_back (record2); + else + mRecords.insert (mRecords.begin()+index, record2); - if (index (mRecords.size())-1) + if (index < size-1) { - for (std::map::iterator iter (mIndex.begin()); iter!=mIndex.end(); - ++iter) - if (iter->second>=index) - ++(iter->second); + for (std::map::iterator iter (mIndex.begin()); iter!=mIndex.end(); ++iter) + { + if (iter->second >= index) + ++(iter->second); + } } mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId ( From 23e7e3c165bb2631f9d8eb298f86da862e91cefa Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 6 Dec 2015 15:14:05 +1100 Subject: [PATCH 331/441] Use std::unique_ptr to store records in collections, RefidCollection and RefIdData. - std::move support required (C++11) - MSVC 2013 or later should be fine --- apps/opencs/model/doc/document.cpp | 25 +++-- apps/opencs/model/doc/savingstages.cpp | 12 +- apps/opencs/model/tools/mergestages.cpp | 21 ++-- apps/opencs/model/tools/mergestages.hpp | 4 +- apps/opencs/model/world/collection.hpp | 105 +++++++++--------- apps/opencs/model/world/collectionbase.hpp | 5 +- apps/opencs/model/world/commands.cpp | 14 +-- apps/opencs/model/world/commands.hpp | 5 +- apps/opencs/model/world/data.cpp | 7 +- apps/opencs/model/world/idcollection.hpp | 33 +++--- apps/opencs/model/world/idtable.cpp | 7 +- apps/opencs/model/world/idtable.hpp | 3 +- apps/opencs/model/world/infocollection.cpp | 45 ++++---- apps/opencs/model/world/infocollection.hpp | 2 +- .../opencs/model/world/nestedidcollection.hpp | 32 +++--- .../model/world/nestedinfocollection.cpp | 32 +++--- apps/opencs/model/world/record.hpp | 18 +-- apps/opencs/model/world/refcollection.cpp | 23 ++-- apps/opencs/model/world/refidcollection.cpp | 15 +-- apps/opencs/model/world/refidcollection.hpp | 4 +- apps/opencs/model/world/refiddata.cpp | 12 +- apps/opencs/model/world/refiddata.hpp | 53 +++++---- 22 files changed, 248 insertions(+), 229 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 18dc9af4a..766503643 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -2091,10 +2092,10 @@ void CSMDoc::Document::addOptionalGmst (const ESM::GameSetting& gmst) { if (getData().getGmsts().searchId (gmst.mId)==-1) { - CSMWorld::Record record; - record.mBase = gmst; - record.mState = CSMWorld::RecordBase::State_BaseOnly; - getData().getGmsts().appendRecord (record); + std::unique_ptr > record(new CSMWorld::Record); + record->mBase = gmst; + record->mState = CSMWorld::RecordBase::State_BaseOnly; + getData().getGmsts().appendRecord (std::move(record)); } } @@ -2102,10 +2103,10 @@ void CSMDoc::Document::addOptionalGlobal (const ESM::Global& global) { if (getData().getGlobals().searchId (global.mId)==-1) { - CSMWorld::Record record; - record.mBase = global; - record.mState = CSMWorld::RecordBase::State_BaseOnly; - getData().getGlobals().appendRecord (record); + std::unique_ptr > record(new CSMWorld::Record); + record->mBase = global; + record->mState = CSMWorld::RecordBase::State_BaseOnly; + getData().getGlobals().appendRecord (std::move(record)); } } @@ -2113,10 +2114,10 @@ void CSMDoc::Document::addOptionalMagicEffect (const ESM::MagicEffect& magicEffe { if (getData().getMagicEffects().searchId (magicEffect.mId)==-1) { - CSMWorld::Record record; - record.mBase = magicEffect; - record.mState = CSMWorld::RecordBase::State_BaseOnly; - getData().getMagicEffects().appendRecord (record); + std::unique_ptr > record(new CSMWorld::Record); + record->mBase = magicEffect; + record->mState = CSMWorld::RecordBase::State_BaseOnly; + getData().getMagicEffects().appendRecord (std::move(record)); } } diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index db38c4779..e18f2311f 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -118,7 +118,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) + if ((*iter)->isModified() || (*iter)->mState == CSMWorld::RecordBase::State_Deleted) { infoModified = true; break; @@ -144,9 +144,9 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message // write modified selected info records for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) + if ((*iter)->isModified() || (*iter)->mState == CSMWorld::RecordBase::State_Deleted) { - ESM::DialInfo info = iter->get(); + ESM::DialInfo info = (*iter)->get(); info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); info.mPrev = ""; @@ -155,7 +155,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message CSMWorld::InfoCollection::RecordConstIterator prev = iter; --prev; - info.mPrev = prev->get().mId.substr (prev->get().mId.find_last_of ('#')+1); + info.mPrev = (*prev)->get().mId.substr ((*prev)->get().mId.find_last_of ('#')+1); } CSMWorld::InfoCollection::RecordConstIterator next = iter; @@ -164,11 +164,11 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message info.mNext = ""; if (next!=range.second) { - info.mNext = next->get().mId.substr (next->get().mId.find_last_of ('#')+1); + info.mNext = (*next)->get().mId.substr ((*next)->get().mId.find_last_of ('#')+1); } writer.startRecord (info.sRecordId); - info.save (writer, iter->mState == CSMWorld::RecordBase::State_Deleted); + info.save (writer, (*iter)->mState == CSMWorld::RecordBase::State_Deleted); writer.endRecord (info.sRecordId); } } diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index d936be593..105a722d0 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -100,10 +100,9 @@ void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messa ref.mRefNum.mIndex = mIndex[Misc::StringUtils::lowerCase (ref.mCell)]++; ref.mRefNum.mContentFile = 0; - CSMWorld::Record newRecord ( - CSMWorld::RecordBase::State_ModifiedOnly, 0, &ref); - - mState.mTarget->getData().getReferences().appendRecord (newRecord); + mState.mTarget->getData().getReferences().appendRecord ( + std::make_unique >( + CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, 0, &ref))); } } @@ -189,10 +188,9 @@ void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& mes texture.mIndex = mNext->second-1; texture.mId = stream2.str(); - CSMWorld::Record newRecord ( - CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture); - - mState.mTarget->getData().getLandTextures().appendRecord (newRecord); + mState.mTarget->getData().getLandTextures().appendRecord ( + std::make_unique >( + CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture))); found = true; } @@ -250,9 +248,8 @@ void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) } } - CSMWorld::Record newRecord ( - CSMWorld::RecordBase::State_ModifiedOnly, 0, &newLand); - - mState.mTarget->getData().getLand().appendRecord (newRecord); + mState.mTarget->getData().getLand().appendRecord ( + std::make_unique >( + CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, 0, &newLand))); } } diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index f88f5be9f..cfb7852dc 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -82,7 +83,8 @@ namespace CSMTools const CSMWorld::Record& record = source.getRecord (stage); if (!record.isDeleted()) - target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_ModifiedOnly, 0, &record.get())); + target.appendRecord (std::make_unique >( + CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, 0, &record.get()))); } class MergeRefIdsStage : public CSMDoc::Stage diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index a13673461..475a26263 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -49,7 +50,7 @@ namespace CSMWorld private: - std::vector > mRecords; + std::vector > > mRecords; std::map mIndex; std::vector *> mColumns; @@ -61,7 +62,7 @@ namespace CSMWorld const std::map& getIdMap() const; - const std::vector >& getRecords() const; + const std::vector > >& getRecords() const; bool reorderRowsImp (int baseIndex, const std::vector& newOrder); ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices @@ -112,12 +113,12 @@ namespace CSMWorld ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) - virtual void replace (int index, const RecordBase& record); + virtual void replace (int index, std::unique_ptr record); ///< If the record type does not match, an exception is thrown. /// /// \attention \a record must not change the ID. - virtual void appendRecord (const RecordBase& record, + virtual void appendRecord (std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None); ///< If the record type does not match, an exception is thrown. ///< \param type Will be ignored, unless the collection supports multiple record types @@ -135,7 +136,7 @@ namespace CSMWorld /// /// \param listDeleted include deleted record in the list - virtual void insertRecord (const RecordBase& record, int index, + virtual void insertRecord (std::unique_ptr record, int index, UniversalId::Type type = UniversalId::Type_None); ///< Insert record before index. /// @@ -152,7 +153,7 @@ namespace CSMWorld void addColumn (Column *column); - void setRecord (int index, const Record& record); + void setRecord (int index, std::unique_ptr > record); ///< \attention This function must not change the ID. NestableColumn *getNestableColumn (int column) const; @@ -165,7 +166,7 @@ namespace CSMWorld } template - const std::vector >& Collection::getRecords() const + const std::vector > >& Collection::getRecords() const { return mRecords; } @@ -185,15 +186,15 @@ namespace CSMWorld return false; // reorder records - std::vector > buffer (size); + std::vector > > buffer (size); for (int i=0; isetModified (buffer[newOrder[i]]->get()); } - std::copy (buffer.begin(), buffer.end(), mRecords.begin()+baseIndex); + std::move (buffer.begin(), buffer.end(), mRecords.begin()+baseIndex); // adjust index for (std::map::iterator iter (mIndex.begin()); iter!=mIndex.end(); @@ -210,12 +211,12 @@ namespace CSMWorld const std::string& destination, const UniversalId::Type type) { - Record copy; - copy.mModified = getRecord(origin).get(); - copy.mState = RecordBase::State_ModifiedOnly; - copy.get().mId = destination; + std::unique_ptr > copy(new Record); + copy->mModified = getRecord(origin).get(); + copy->mState = RecordBase::State_ModifiedOnly; + copy->get().mId = destination; - insertRecord(copy, getAppendIndex(destination, type)); + insertRecord(std::move(copy), getAppendIndex(destination, type)); } template @@ -238,15 +239,15 @@ namespace CSMWorld if (iter==mIndex.end()) { - Record record2; - record2.mState = Record::State_ModifiedOnly; - record2.mModified = record; + std::unique_ptr > record2(new Record); + record2->mState = Record::State_ModifiedOnly; + record2->mModified = record; - insertRecord (record2, getAppendIndex (id)); + insertRecord (std::move(record2), getAppendIndex (id)); } else { - mRecords[iter->second].setModified (record); + mRecords[iter->second]->setModified (record); } } @@ -259,7 +260,7 @@ namespace CSMWorld template std::string Collection::getId (int index) const { - return IdAccessorT().getId (mRecords.at (index).get()); + return IdAccessorT().getId (mRecords.at (index)->get()); } template @@ -282,13 +283,13 @@ namespace CSMWorld template QVariant Collection::getData (int index, int column) const { - return mColumns.at (column)->get (mRecords.at (index)); + return mColumns.at (column)->get (*mRecords.at (index)); } template void Collection::setData (int index, int column, const QVariant& data) { - return mColumns.at (column)->set (mRecords.at (index), data); + return mColumns.at (column)->set (*mRecords.at (index), data); } template @@ -315,8 +316,8 @@ namespace CSMWorld template void Collection::merge() { - for (typename std::vector >::iterator iter (mRecords.begin()); iter!=mRecords.end(); ++iter) - iter->merge(); + for (typename std::vector > >::iterator iter (mRecords.begin()); iter!=mRecords.end(); ++iter) + (*iter)->merge(); purge(); } @@ -328,7 +329,7 @@ namespace CSMWorld while (i (mRecords.size())) { - if (mRecords[i].isErased()) + if (mRecords[i]->isErased()) removeRows (i, 1); else ++i; @@ -369,11 +370,11 @@ namespace CSMWorld IdAccessorT().getId (record) = id; record.blank(); - Record record2; - record2.mState = Record::State_ModifiedOnly; - record2.mModified = record; + std::unique_ptr > record2(new Record); + record2->mState = Record::State_ModifiedOnly; + record2->mModified = record; - insertRecord (record2, getAppendIndex (id, type), type); + insertRecord (std::move(record2), getAppendIndex (id, type), type); } template @@ -390,18 +391,19 @@ namespace CSMWorld } template - void Collection::replace (int index, const RecordBase& record) + void Collection::replace (int index, std::unique_ptr record) { - mRecords.at (index) = dynamic_cast&> (record); + std::unique_ptr > tmp(static_cast*>(record.release())); + mRecords.at (index) = std::move(tmp); } template - void Collection::appendRecord (const RecordBase& record, + void Collection::appendRecord (std::unique_ptr record, UniversalId::Type type) { - insertRecord (record, - getAppendIndex (IdAccessorT().getId ( - dynamic_cast&> (record).get()), type), type); + int index = + getAppendIndex(IdAccessorT().getId(static_cast*>(record.get())->get()), type); + insertRecord (std::move(record), index, type); } template @@ -419,8 +421,8 @@ namespace CSMWorld for (typename std::map::const_iterator iter = mIndex.begin(); iter!=mIndex.end(); ++iter) { - if (listDeleted || !mRecords[iter->second].isDeleted()) - ids.push_back (IdAccessorT().getId (mRecords[iter->second].get())); + if (listDeleted || !mRecords[iter->second]->isDeleted()) + ids.push_back (IdAccessorT().getId (mRecords[iter->second]->get())); } return ids; @@ -430,29 +432,30 @@ namespace CSMWorld const Record& Collection::getRecord (const std::string& id) const { int index = getIndex (id); - return mRecords.at (index); + return *mRecords.at (index); } template const Record& Collection::getRecord (int index) const { - return mRecords.at (index); + return *mRecords.at (index); } template - void Collection::insertRecord (const RecordBase& record, int index, + void Collection::insertRecord (std::unique_ptr record, int index, UniversalId::Type type) { int size = static_cast(mRecords.size()); if (index < 0 || index > size) throw std::runtime_error ("index out of range"); - const Record& record2 = dynamic_cast&> (record); + std::unique_ptr > record2(static_cast*>(record.release())); + std::string lowerId = Misc::StringUtils::lowerCase(IdAccessorT().getId(record2->get())); if (index == size) - mRecords.push_back (record2); + mRecords.push_back (std::move(record2)); else - mRecords.insert (mRecords.begin()+index, record2); + mRecords.insert (mRecords.begin()+index, std::move(record2)); if (index < size-1) { @@ -463,18 +466,18 @@ namespace CSMWorld } } - mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId ( - record2.get())), index)); + mIndex.insert (std::make_pair (lowerId, index)); } template - void Collection::setRecord (int index, const Record& record) + void Collection::setRecord (int index, + std::unique_ptr > record) { - if (Misc::StringUtils::lowerCase (IdAccessorT().getId (mRecords.at (index).get()))!= - Misc::StringUtils::lowerCase (IdAccessorT().getId (record.get()))) + if (Misc::StringUtils::lowerCase (IdAccessorT().getId (mRecords.at (index)->get())) != + Misc::StringUtils::lowerCase (IdAccessorT().getId (record->get()))) throw std::runtime_error ("attempt to change the ID of a record"); - mRecords.at (index) = record; + mRecords.at (index) = std::move(record); } template diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp index ef826e31c..fa0399e49 100644 --- a/apps/opencs/model/world/collectionbase.hpp +++ b/apps/opencs/model/world/collectionbase.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "universalid.hpp" #include "columns.hpp" @@ -64,13 +65,13 @@ namespace CSMWorld ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) - virtual void replace (int index, const RecordBase& record) = 0; + virtual void replace (int index, std::unique_ptr record) = 0; ///< If the record type does not match, an exception is thrown. /// /// \attention \a record must not change the ID. ///< \param type Will be ignored, unless the collection supports multiple record types - virtual void appendRecord (const RecordBase& record, + virtual void appendRecord (std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None) = 0; ///< If the record type does not match, an exception is thrown. diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index d510cd103..0f6681ba1 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -124,16 +124,15 @@ void CSMWorld::CreateCommand::undo() } CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand* parent) -: QUndoCommand (parent), mModel (model), mId (id), mOld (0) + : QUndoCommand (parent), mModel (model), mId (id) { setText (("Revert record " + id).c_str()); - mOld = model.getRecord (id).clone(); + mOld = std::move(model.getRecord (id).clone()); } CSMWorld::RevertCommand::~RevertCommand() { - delete mOld; } void CSMWorld::RevertCommand::redo() @@ -155,21 +154,20 @@ void CSMWorld::RevertCommand::redo() void CSMWorld::RevertCommand::undo() { - mModel.setRecord (mId, *mOld); + mModel.setRecord (mId, std::move(mOld)); } CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, CSMWorld::UniversalId::Type type, QUndoCommand* parent) -: QUndoCommand (parent), mModel (model), mId (id), mOld (0), mType(type) +: QUndoCommand (parent), mModel (model), mId (id), mType(type) { setText (("Delete record " + id).c_str()); - mOld = model.getRecord (id).clone(); + mOld = std::move(model.getRecord (id).clone()); } CSMWorld::DeleteCommand::~DeleteCommand() { - delete mOld; } void CSMWorld::DeleteCommand::redo() @@ -191,7 +189,7 @@ void CSMWorld::DeleteCommand::redo() void CSMWorld::DeleteCommand::undo() { - mModel.setRecord (mId, *mOld, mType); + mModel.setRecord (mId, std::move(mOld), mType); } diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 05f21017c..5cfad5856 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -97,7 +98,7 @@ namespace CSMWorld { IdTable& mModel; std::string mId; - RecordBase *mOld; + std::unique_ptr mOld; // not implemented RevertCommand (const RevertCommand&); @@ -118,7 +119,7 @@ namespace CSMWorld { IdTable& mModel; std::string mId; - RecordBase *mOld; + std::unique_ptr mOld; UniversalId::Type mType; // not implemented diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 9f92935dc..0e35c4141 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -861,8 +861,8 @@ const CSMWorld::MetaData& CSMWorld::Data::getMetaData() const void CSMWorld::Data::setMetaData (const MetaData& metaData) { - Record record (RecordBase::State_ModifiedOnly, 0, &metaData); - mMetaData.setRecord (0, record); + mMetaData.setRecord (0, std::make_unique >( + Record(RecordBase::State_ModifiedOnly, 0, &metaData))); } const CSMWorld::NpcAutoCalc& CSMWorld::Data::getNpcAutoCalc() const @@ -920,7 +920,8 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base metaData.mId = "sys::meta"; metaData.load (*mReader); - mMetaData.setRecord (0, Record (RecordBase::State_ModifiedOnly, 0, &metaData)); + mMetaData.setRecord (0, std::make_unique >( + Record (RecordBase::State_ModifiedOnly, 0, &metaData))); } return mReader->getRecordCount(); diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index ea6eefb88..5a7d2410d 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -66,9 +66,9 @@ namespace CSMWorld return -1; } - Record baseRecord = this->getRecord (index); - baseRecord.mState = RecordBase::State_Deleted; - this->setRecord (index, baseRecord); + std::unique_ptr > baseRecord(new Record(this->getRecord(index))); + baseRecord->mState = RecordBase::State_Deleted; + this->setRecord(index, std::move(baseRecord)); return index; } @@ -79,30 +79,31 @@ namespace CSMWorld int IdCollection::load (const ESXRecordT& record, bool base, int index) { - if (index==-2) + if (index==-2) // index unknown index = this->searchId (IdAccessorT().getId (record)); if (index==-1) { // new record - Record record2; - record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - (base ? record2.mBase : record2.mModified) = record; + std::unique_ptr > record2(new Record); + record2->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record2->mBase : record2->mModified) = record; index = this->getSize(); - this->appendRecord (record2); + this->appendRecord(std::move(record2)); } else { // old record - Record record2 = Collection::getRecord (index); + std::unique_ptr > record2( + new Record(Collection::getRecord(index))); if (base) - record2.mBase = record; + record2->mBase = record; else - record2.setModified (record); + record2->setModified(record); - this->setRecord (index, record2); + this->setRecord(index, std::move(record2)); } return index; @@ -116,7 +117,7 @@ namespace CSMWorld if (index==-1) return false; - Record record = Collection::getRecord (index); + const Record& record = Collection::getRecord (index); if (record.isDeleted()) return false; @@ -127,8 +128,10 @@ namespace CSMWorld } else { - record.mState = RecordBase::State_Deleted; - this->setRecord (index, record); + std::unique_ptr > record2( + new Record(Collection::getRecord(index))); + record2->mState = RecordBase::State_Deleted; + this->setRecord(index, std::move(record2)); } return true; diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index e6acf77de..ec666b3df 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -166,7 +166,8 @@ QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) return index(mIdCollection->getIndex (id), column); } -void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& record, CSMWorld::UniversalId::Type type) +void CSMWorld::IdTable::setRecord (const std::string& id, + std::unique_ptr record, CSMWorld::UniversalId::Type type) { int index = mIdCollection->searchId (id); @@ -176,13 +177,13 @@ void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& reco beginInsertRows (QModelIndex(), index2, index2); - mIdCollection->appendRecord (record, type); + mIdCollection->appendRecord (std::move(record), type); endInsertRows(); } else { - mIdCollection->replace (index, record); + mIdCollection->replace (index, std::move(record)); emit dataChanged (CSMWorld::IdTable::index (index, 0), CSMWorld::IdTable::index (index, mIdCollection->getColumns()-1)); } diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 9ecba0214..7219e5a48 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -2,6 +2,7 @@ #define CSM_WOLRD_IDTABLE_H #include +#include #include "idtablebase.hpp" #include "universalid.hpp" @@ -59,7 +60,7 @@ namespace CSMWorld virtual QModelIndex getModelIndex (const std::string& id, int column) const; - void setRecord (const std::string& id, const RecordBase& record, + void setRecord (const std::string& id, std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None); ///< Add record or overwrite existing recrod. diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 083d07da0..c331a2d73 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -15,25 +15,25 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) if (index==-1) { // new record - Record record2; - record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - (base ? record2.mBase : record2.mModified) = record; + std::unique_ptr > record2(new Record); + record2->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record2->mBase : record2->mModified) = record; int index2 = -1; - std::string topic = Misc::StringUtils::lowerCase (record2.get().mTopicId); + std::string topic = Misc::StringUtils::lowerCase (record2->get().mTopicId); - if (!record2.get().mPrev.empty()) + if (!record2->get().mPrev.empty()) { - index2 = getInfoIndex (record2.get().mPrev, topic); + index2 = getInfoIndex (record2->get().mPrev, topic); if (index2!=-1) ++index2; } - if (index2==-1 && !record2.get().mNext.empty()) + if (index2==-1 && !record2->get().mNext.empty()) { - index2 = getInfoIndex (record2.get().mNext, topic); + index2 = getInfoIndex (record2->get().mNext, topic); } if (index2==-1) @@ -43,19 +43,19 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) index2 = std::distance (getRecords().begin(), range.second); } - insertRecord (record2, index2); + insertRecord (std::move(record2), index2); } else { // old record - Record record2 = getRecord (index); + std::unique_ptr > record2(new Record(getRecord(index))); if (base) - record2.mBase = record; + record2->mBase = record; else - record2.setModified (record); + record2->setModified (record); - setRecord (index, record2); + setRecord (index, std::move(record2)); } } @@ -66,7 +66,7 @@ int CSMWorld::InfoCollection::getInfoIndex (const std::string& id, const std::st std::pair range = getTopicRange (topic); for (; range.first!=range.second; ++range.first) - if (Misc::StringUtils::ciEqual(range.first->get().mId, fullId)) + if (Misc::StringUtils::ciEqual((*range.first).get()->get().mId, fullId)) return std::distance (getRecords().begin(), range.first); return -1; @@ -128,9 +128,9 @@ void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ES } else { - Record record = getRecord (index); - record.mState = RecordBase::State_Deleted; - setRecord (index, record); + std::unique_ptr > record(new Record(getRecord(index))); + record->mState = RecordBase::State_Deleted; + setRecord (index, std::move(record)); } } else @@ -171,7 +171,7 @@ CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const s while (begin != getRecords().begin()) { - if (!Misc::StringUtils::ciEqual(begin->get().mTopicId, topic2)) + if (!Misc::StringUtils::ciEqual((*begin)->get().mTopicId, topic2)) { // we've gone one too far, go back ++begin; @@ -184,7 +184,7 @@ CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const s RecordConstIterator end = begin; for (; end!=getRecords().end(); ++end) - if (!Misc::StringUtils::ciEqual(end->get().mTopicId, topic2)) + if (!Misc::StringUtils::ciEqual((*end)->get().mTopicId, topic2)) break; return Range (begin, end); @@ -199,7 +199,7 @@ void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId std::map::const_iterator end = getIdMap().end(); for (; current != end; ++current) { - Record record = getRecord(current->second); + const Record& record = getRecord(current->second); if (Misc::StringUtils::ciEqual(dialogueId, record.get().mTopicId)) { @@ -209,8 +209,9 @@ void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId } else { - record.mState = RecordBase::State_Deleted; - setRecord(current->second, record); + std::unique_ptr > record2(new Record(record)); + record2->mState = RecordBase::State_Deleted; + setRecord(current->second, std::move(record2)); } } else diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index e5a5575c7..da389763c 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -15,7 +15,7 @@ namespace CSMWorld { public: - typedef std::vector >::const_iterator RecordConstIterator; + typedef std::vector > >::const_iterator RecordConstIterator; typedef std::pair Range; private: diff --git a/apps/opencs/model/world/nestedidcollection.hpp b/apps/opencs/model/world/nestedidcollection.hpp index 56b112365..08ed70d42 100644 --- a/apps/opencs/model/world/nestedidcollection.hpp +++ b/apps/opencs/model/world/nestedidcollection.hpp @@ -90,23 +90,23 @@ namespace CSMWorld template void NestedIdCollection::addNestedRow(int row, int column, int position) { - Record record; - record.assign(Collection::getRecord(row)); + std::unique_ptr > record(new Record); + record->assign(Collection::getRecord(row)); - getAdapter(Collection::getColumn(column)).addRow(record, position); + getAdapter(Collection::getColumn(column)).addRow(*record, position); - Collection::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } template void NestedIdCollection::removeNestedRows(int row, int column, int subRow) { - Record record; - record.assign(Collection::getRecord(row)); + std::unique_ptr > record(new Record); + record->assign(Collection::getRecord(row)); - getAdapter(Collection::getColumn(column)).removeRow(record, subRow); + getAdapter(Collection::getColumn(column)).removeRow(*record, subRow); - Collection::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } template @@ -121,13 +121,13 @@ namespace CSMWorld void NestedIdCollection::setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) { - Record record; - record.assign(Collection::getRecord(row)); + std::unique_ptr > record(new Record); + record->assign(Collection::getRecord(row)); getAdapter(Collection::getColumn(column)).setData( - record, data, subRow, subColumn); + *record, data, subRow, subColumn); - Collection::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } template @@ -142,13 +142,13 @@ namespace CSMWorld void NestedIdCollection::setNestedTable(int row, int column, const CSMWorld::NestedTableWrapperBase& nestedTable) { - Record record; - record.assign(Collection::getRecord(row)); + std::unique_ptr > record(new Record); + record->assign(Collection::getRecord(row)); getAdapter(Collection::getColumn(column)).setTable( - record, nestedTable); + *record, nestedTable); - Collection::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } template diff --git a/apps/opencs/model/world/nestedinfocollection.cpp b/apps/opencs/model/world/nestedinfocollection.cpp index 4abaaf9c0..d404bb9a6 100644 --- a/apps/opencs/model/world/nestedinfocollection.cpp +++ b/apps/opencs/model/world/nestedinfocollection.cpp @@ -35,22 +35,22 @@ namespace CSMWorld void NestedInfoCollection::addNestedRow(int row, int column, int position) { - Record record; - record.assign(Collection >::getRecord(row)); + std::unique_ptr > record(new Record); + record->assign(Collection >::getRecord(row)); - getAdapter(Collection >::getColumn(column)).addRow(record, position); + getAdapter(Collection >::getColumn(column)).addRow(*record, position); - Collection >::setRecord(row, record); + Collection >::setRecord(row, std::move(record)); } void NestedInfoCollection::removeNestedRows(int row, int column, int subRow) { - Record record; - record.assign(Collection >::getRecord(row)); + std::unique_ptr > record(new Record); + record->assign(Collection >::getRecord(row)); - getAdapter(Collection >::getColumn(column)).removeRow(record, subRow); + getAdapter(Collection >::getColumn(column)).removeRow(*record, subRow); - Collection >::setRecord(row, record); + Collection >::setRecord(row, std::move(record)); } QVariant NestedInfoCollection::getNestedData (int row, @@ -63,13 +63,13 @@ namespace CSMWorld void NestedInfoCollection::setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) { - Record record; - record.assign(Collection >::getRecord(row)); + std::unique_ptr > record(new Record); + record->assign(Collection >::getRecord(row)); getAdapter(Collection >::getColumn(column)).setData( - record, data, subRow, subColumn); + *record, data, subRow, subColumn); - Collection >::setRecord(row, record); + Collection >::setRecord(row, std::move(record)); } CSMWorld::NestedTableWrapperBase* NestedInfoCollection::nestedTable(int row, @@ -82,13 +82,13 @@ namespace CSMWorld void NestedInfoCollection::setNestedTable(int row, int column, const CSMWorld::NestedTableWrapperBase& nestedTable) { - Record record; - record.assign(Collection >::getRecord(row)); + std::unique_ptr > record(new Record); + record->assign(Collection >::getRecord(row)); getAdapter(Collection >::getColumn(column)).setTable( - record, nestedTable); + *record, nestedTable); - Collection >::setRecord(row, record); + Collection >::setRecord(row, std::move(record)); } int NestedInfoCollection::getNestedRowsCount(int row, int column) const diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index 3362f9f96..91e4c3c42 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -1,6 +1,7 @@ #ifndef CSM_WOLRD_RECORD_H #define CSM_WOLRD_RECORD_H +#include #include namespace CSMWorld @@ -20,9 +21,9 @@ namespace CSMWorld virtual ~RecordBase(); - virtual RecordBase *clone() const = 0; + virtual std::unique_ptr clone() const = 0; - virtual RecordBase *modifiedCopy() const = 0; + virtual std::unique_ptr modifiedCopy() const = 0; virtual void assign (const RecordBase& record) = 0; ///< Will throw an exception if the types don't match. @@ -45,9 +46,9 @@ namespace CSMWorld Record(State state, const ESXRecordT *base = 0, const ESXRecordT *modified = 0); - virtual RecordBase *clone() const; + virtual std::unique_ptr clone() const; - virtual RecordBase *modifiedCopy() const; + virtual std::unique_ptr modifiedCopy() const; virtual void assign (const RecordBase& record); @@ -85,15 +86,16 @@ namespace CSMWorld } template - RecordBase *Record::modifiedCopy() const + std::unique_ptr Record::modifiedCopy() const { - return new Record (State_ModifiedOnly, 0, &(this->get())); + return std::make_unique >( + Record(State_ModifiedOnly, 0, &(this->get()))); } template - RecordBase *Record::clone() const + std::unique_ptr Record::clone() const { - return new Record (*this); + return std::make_unique >(Record(*this)); } template diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 3f3688fb7..2568a5120 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -92,8 +92,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool int index = getIndex (iter->second); - Record record = getRecord (index); - if (base) { removeRows (index, 1); @@ -101,8 +99,9 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool } else { - record.mState = RecordBase::State_Deleted; - setRecord (index, record); + std::unique_ptr > record2(new Record(getRecord(index))); + record2->mState = RecordBase::State_Deleted; + setRecord(index, std::move(record2)); } continue; @@ -113,11 +112,11 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // new reference ref.mId = getNewId(); - Record record; - record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - (base ? record.mBase : record.mModified) = ref; + std::unique_ptr > record(new Record); + record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record->mBase : record->mModified) = ref; - appendRecord (record); + appendRecord(std::move(record)); cache.insert (std::make_pair (ref.mRefNum, ref.mId)); } @@ -128,11 +127,11 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool int index = getIndex (ref.mId); - Record record = getRecord (index); - record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; - (base ? record.mBase : record.mModified) = ref; + std::unique_ptr > record(new Record(getRecord(index))); + record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; + (base ? record->mBase : record->mModified) = ref; - setRecord (index, record); + setRecord(index, std::move(record)); } } } diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 9560539b2..7737e62c0 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -810,30 +810,31 @@ int CSMWorld::RefIdCollection::searchId (const std::string& id) const return mData.localToGlobalIndex (localIndex); } -void CSMWorld::RefIdCollection::replace (int index, const RecordBase& record) +void CSMWorld::RefIdCollection::replace (int index, std::unique_ptr record) { - mData.getRecord (mData.globalToLocalIndex (index)).assign (record); + mData.getRecord (mData.globalToLocalIndex (index)).assign (*record.release()); } void CSMWorld::RefIdCollection::cloneRecord(const std::string& origin, const std::string& destination, const CSMWorld::UniversalId::Type type) { - std::auto_ptr newRecord(mData.getRecord(mData.searchId(origin)).modifiedCopy()); + std::unique_ptr newRecord = + std::move(mData.getRecord(mData.searchId(origin)).modifiedCopy()); mAdapters.find(type)->second->setId(*newRecord, destination); - mData.insertRecord(*newRecord, type, destination); + mData.insertRecord(std::move(newRecord), type, destination); } -void CSMWorld::RefIdCollection::appendRecord (const RecordBase& record, +void CSMWorld::RefIdCollection::appendRecord (std::unique_ptr record, UniversalId::Type type) { - std::string id = findAdapter (type).getId (record); + std::string id = findAdapter (type).getId (*record.get()); int index = mData.getAppendIndex (type); mData.appendRecord (type, id, false); - mData.getRecord (mData.globalToLocalIndex (index)).assign (record); + mData.getRecord (mData.globalToLocalIndex (index)).assign (*record.release()); } const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (const std::string& id) const diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index 4db39348e..f749e7df7 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -89,12 +89,12 @@ namespace CSMWorld ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) - virtual void replace (int index, const RecordBase& record); + virtual void replace (int index, std::unique_ptr record); ///< If the record type does not match, an exception is thrown. /// /// \attention \a record must not change the ID. - virtual void appendRecord (const RecordBase& record, UniversalId::Type type); + virtual void appendRecord (std::unique_ptr record, UniversalId::Type type); ///< If the record type does not match, an exception is thrown. /// ///< \param type Will be ignored, unless the collection supports multiple record types diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 2d8c9ac10..298a62021 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -367,7 +367,7 @@ const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStati return mStatics; } -void CSMWorld::RefIdData::insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) +void CSMWorld::RefIdData::insertRecord (std::unique_ptr record, CSMWorld::UniversalId::Type type, const std::string& id) { std::map::iterator iter = mRecordContainers.find (type); @@ -375,7 +375,7 @@ void CSMWorld::RefIdData::insertRecord (CSMWorld::RecordBase& record, CSMWorld:: if (iter==mRecordContainers.end()) throw std::logic_error ("invalid local index type"); - iter->second->insertRecord(record); + iter->second->insertRecord(std::move(record)); mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), LocalIndex (iter->second->getSize()-1, type))); @@ -387,9 +387,7 @@ void CSMWorld::RefIdData::copyTo (int index, RefIdData& target) const RefIdDataContainerBase *source = mRecordContainers.find (localIndex.second)->second; - std::string id = source->getId (localIndex.first); - - std::auto_ptr newRecord (source->getRecord (localIndex.first).modifiedCopy()); - - target.insertRecord (*newRecord, localIndex.second, id); + target.insertRecord(source->getRecord(localIndex.first).modifiedCopy(), + localIndex.second, + source->getId(localIndex.first)); } diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 59cad6a66..4a0900e6d 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -49,7 +50,7 @@ namespace CSMWorld virtual void appendRecord (const std::string& id, bool base) = 0; - virtual void insertRecord (RecordBase& record) = 0; + virtual void insertRecord (std::unique_ptr record) = 0; virtual int load (ESM::ESMReader& reader, bool base) = 0; ///< \return index of a loaded record or -1 if no record was loaded @@ -64,7 +65,7 @@ namespace CSMWorld template struct RefIdDataContainer : public RefIdDataContainerBase { - std::vector > mContainer; + std::vector > > mContainer; virtual int getSize() const; @@ -74,7 +75,7 @@ namespace CSMWorld virtual void appendRecord (const std::string& id, bool base); - virtual void insertRecord (RecordBase& record); + virtual void insertRecord (std::unique_ptr record); virtual int load (ESM::ESMReader& reader, bool base); ///< \return index of a loaded record or -1 if no record was loaded @@ -87,10 +88,18 @@ namespace CSMWorld }; template - void RefIdDataContainer::insertRecord(RecordBase& record) + void RefIdDataContainer::insertRecord(std::unique_ptr record) { - Record& newRecord = dynamic_cast& >(record); - mContainer.push_back(newRecord); + Record *tmp = dynamic_cast*>(record.get()); + if(tmp != nullptr) + { + record.release(); + std::unique_ptr > newRecord; + newRecord.reset(tmp); + mContainer.push_back(std::move(newRecord)); + } + else + throw std::runtime_error ("invalid record for RefIdDataContainer"); } template @@ -102,27 +111,27 @@ namespace CSMWorld template const RecordBase& RefIdDataContainer::getRecord (int index) const { - return mContainer.at (index); + return *mContainer.at (index); } template RecordBase& RefIdDataContainer::getRecord (int index) { - return mContainer.at (index); + return *mContainer.at (index); } template void RefIdDataContainer::appendRecord (const std::string& id, bool base) { - Record record; + std::unique_ptr > record(new Record); - record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - record.mBase.mId = id; - record.mModified.mId = id; - (base ? record.mBase : record.mModified).blank(); + record->mBase.mId = id; + record->mModified.mId = id; + (base ? record->mBase : record->mModified).blank(); - mContainer.push_back (record); + mContainer.push_back (std::move(record)); } template @@ -137,7 +146,7 @@ namespace CSMWorld int numRecords = static_cast(mContainer.size()); for (; index < numRecords; ++index) { - if (Misc::StringUtils::ciEqual(mContainer[index].get().mId, record.mId)) + if (Misc::StringUtils::ciEqual(mContainer[index]->get().mId, record.mId)) { break; } @@ -155,7 +164,7 @@ namespace CSMWorld // Flag the record as Deleted even for a base content file. // RefIdData is responsible for its erasure. - mContainer[index].mState = RecordBase::State_Deleted; + mContainer[index]->mState = RecordBase::State_Deleted; } else { @@ -164,16 +173,16 @@ namespace CSMWorld appendRecord(record.mId, base); if (base) { - mContainer.back().mBase = record; + mContainer.back()->mBase = record; } else { - mContainer.back().mModified = record; + mContainer.back()->mModified = record; } } else if (!base) { - mContainer[index].setModified(record); + mContainer[index]->setModified(record); } } @@ -192,13 +201,13 @@ namespace CSMWorld template std::string RefIdDataContainer::getId (int index) const { - return mContainer.at (index).get().mId; + return mContainer.at (index)->get().mId; } template void RefIdDataContainer::save (int index, ESM::ESMWriter& writer) const { - Record record = mContainer.at(index); + const Record& record = *mContainer.at(index); if (record.isModified() || record.mState == RecordBase::State_Deleted) { @@ -260,7 +269,7 @@ namespace CSMWorld void erase (int index, int count); - void insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, + void insertRecord (std::unique_ptr record, CSMWorld::UniversalId::Type type, const std::string& id); const RecordBase& getRecord (const LocalIndex& index) const; From 19af94b73eb4d8974f16f48fbda7ed87d561508c Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 6 Dec 2015 15:20:45 +1100 Subject: [PATCH 332/441] Reduce copying further by adding move constructors and move assignment operators to CellRef structs. --- apps/opencs/model/world/ref.cpp | 22 ++++++++ apps/opencs/model/world/ref.hpp | 5 ++ apps/opencs/model/world/refcollection.cpp | 4 +- components/esm/cellref.cpp | 61 +++++++++++++++++++++++ components/esm/cellref.hpp | 9 ++++ 5 files changed, 99 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index 638f7ec9c..8c6f1d836 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -4,10 +4,32 @@ CSMWorld::CellRef::CellRef() { + mId.clear(); + mCell.clear(); + mOriginalCell.clear(); + mRefNum.mIndex = 0; mRefNum.mContentFile = 0; } +CSMWorld::CellRef::CellRef (CSMWorld::CellRef&& other) : ESM::CellRef (other) +{ + *this = std::move(other); +} + +CSMWorld::CellRef& CSMWorld::CellRef::operator= (CSMWorld::CellRef&& other) +{ + if (this != &other) + { + ESM::CellRef::operator= (other); + mId = std::move(other.mId); + mCell = std::move(other.mCell); + mOriginalCell = std::move(other.mOriginalCell); + } + + return *this; +} + std::pair CSMWorld::CellRef::getCellIndex() const { const int cellSize = 8192; diff --git a/apps/opencs/model/world/ref.hpp b/apps/opencs/model/world/ref.hpp index c60392221..c439e7ca5 100644 --- a/apps/opencs/model/world/ref.hpp +++ b/apps/opencs/model/world/ref.hpp @@ -15,6 +15,11 @@ namespace CSMWorld std::string mOriginalCell; CellRef(); + CellRef(const CellRef&) = default; + CellRef& operator= (const CellRef&) = default; + + CellRef (CellRef&& other); + CellRef& operator= (CellRef&& other); /// Calculate cell index based on coordinates (x and y) std::pair getCellIndex() const; diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 2568a5120..117aeb012 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -114,7 +114,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool std::unique_ptr > record(new Record); record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - (base ? record->mBase : record->mModified) = ref; + (base ? record->mBase : record->mModified) = std::move(ref); appendRecord(std::move(record)); @@ -129,7 +129,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool std::unique_ptr > record(new Record(getRecord(index))); record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; - (base ? record->mBase : record->mModified) = ref; + (base ? record->mBase : record->mModified) = std::move(ref); setRecord(index, std::move(record)); } diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 76a82fe23..b3fb410e3 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -206,6 +206,67 @@ void ESM::CellRef::blank() } } +ESM::CellRef::CellRef () +{ + blank(); +} + +ESM::CellRef::CellRef (const CellRef& other) + : mRefNum(other.mRefNum) + , mRefID(other.mRefID) + , mScale(other.mScale) + , mOwner(other.mOwner) + , mGlobalVariable(other.mGlobalVariable) + , mSoul(other.mSoul) + , mFaction(other.mFaction) + , mFactionRank(other.mFactionRank) + , mChargeInt(other.mChargeInt) + , mEnchantmentCharge(other.mEnchantmentCharge) + , mGoldValue(other.mGoldValue) + , mTeleport(other.mTeleport) + , mDoorDest(other.mDoorDest) + , mDestCell(other.mDestCell) + , mLockLevel(other.mLockLevel) + , mKey(other.mKey) + , mTrap(other.mTrap) + , mReferenceBlocked(other.mReferenceBlocked) + , mPos(other.mPos) +{ +} + +ESM::CellRef::CellRef (CellRef&& other) +{ + *this = std::move(other); +} + +ESM::CellRef& ESM::CellRef::operator= (CellRef&& other) +{ + if (this != &other) + { + mRefNum = other.mRefNum; // RefNum + mRefID = std::move(other.mRefID); + mScale = other.mScale; + mOwner = std::move(other.mOwner); + mGlobalVariable = std::move(other.mGlobalVariable); + mSoul = std::move(other.mSoul); + mFaction = std::move(other.mFaction); + mFactionRank = other.mFactionRank; + mChargeInt = other.mChargeInt; + mEnchantmentCharge = other.mEnchantmentCharge; + mGoldValue = other.mGoldValue; + mTeleport = other.mTeleport; + mDoorDest = other.mDoorDest; // Position + mDestCell = std::move(other.mDestCell); + mLockLevel = other.mLockLevel; + mKey = std::move(other.mKey); + mTrap = std::move(other.mTrap); + mReferenceBlocked = other.mReferenceBlocked; + mPos = other.mPos; // Position + } + + return *this; +} + bool ESM::operator== (const RefNum& left, const RefNum& right) { return left.mIndex==right.mIndex && left.mContentFile==right.mContentFile; diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index c371a4f01..f84386b3a 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -109,6 +109,15 @@ namespace ESM void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false, bool isDeleted = false) const; void blank(); + + CellRef(); + ~CellRef() = default; + + CellRef(const CellRef&); + CellRef& operator=(const CellRef&) = default; + + CellRef (CellRef&& other); + CellRef& operator=(CellRef&& other); }; bool operator== (const RefNum& left, const RefNum& right); From 65df15a89d7fa129dc92bb6c2538aa4befccd8ab Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 6 Dec 2015 15:26:20 +1100 Subject: [PATCH 333/441] Suppress MSVC 2015 warnings about hidden/deleted base class move constructors and move assignment operators. --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd01db606..4395df940 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -704,6 +704,11 @@ if (WIN32) 4800 # Boolean optimization warning, e.g. myBool = (myInt != 0) instead of myBool = myInt ) + # MSVC 2015 + if (MSVC_VERSION GREATER 1899) + set(WARNINGS_DISABLE ${WARNINGS_DISABLE} 5026 5027) + endif() + foreach(d ${WARNINGS_DISABLE}) set(WARNINGS "${WARNINGS} /wd${d}") endforeach(d) From ff072441fde05d189cb06cb44cc9c360690c2f09 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 6 Dec 2015 19:48:56 +1100 Subject: [PATCH 334/441] Change the loader's top progress bar to show total number of records processed rather than the number of files. --- apps/opencs/model/doc/loader.cpp | 8 ++--- apps/opencs/model/world/data.cpp | 16 +++++++++ apps/opencs/model/world/data.hpp | 2 ++ apps/opencs/view/doc/loader.cpp | 56 +++++++++++++++++--------------- apps/opencs/view/doc/loader.hpp | 8 +++-- 5 files changed, 56 insertions(+), 34 deletions(-) diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index b522f89f2..993e076a4 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -77,19 +77,19 @@ void CSMDoc::Loader::load() return; } - if (iter->second.mFilesecond.mFilegetContentFiles()[iter->second.mFile]; - int steps = document->getData().startLoading (path, iter->second.mFile!=editedIndex, false); + int steps = document->getData().startLoading (path, iter->second.mFile!=editedIndex, /*project*/false); iter->second.mRecordsLeft = true; iter->second.mRecordsLoaded = 0; emit nextStage (document, path.filename().string(), steps); } - else if (iter->second.mFile==size) + else if (iter->second.mFile==size) // start loading the last (project) file { - int steps = document->getData().startLoading (document->getProjectPath(), false, true); + int steps = document->getData().startLoading (document->getProjectPath(), /*base*/false, true); iter->second.mRecordsLeft = true; iter->second.mRecordsLoaded = 0; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 0e35c4141..d8916935d 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -897,6 +897,22 @@ void CSMWorld::Data::merge() mGlobals.merge(); } +int CSMWorld::Data::getTotalRecords (const std::vector& files) +{ + int records = 0; + + std::unique_ptr reader = std::unique_ptr(new ESM::ESMReader); + + for (unsigned int i = 0; i < files.size(); ++i) + { + reader->open(files[i].string()); + records += reader->getRecordCount(); + reader->close(); + } + + return records; +} + int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base, bool project) { // Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand loading diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index a3ccb28be..d59b83607 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -260,6 +260,8 @@ namespace CSMWorld void merge(); ///< Merge modified into base. + int getTotalRecords (const std::vector& files); // for better loading bar + int startLoading (const boost::filesystem::path& path, bool base, bool project); ///< Begin merging content of a file into base or modified. /// diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index 713295d70..36cf42c24 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -17,7 +17,7 @@ void CSVDoc::LoadingDocument::closeEvent (QCloseEvent *event) } CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) -: mDocument (document), mAborted (false), mMessages (0), mTotalRecords (0) +: mDocument (document), mAborted (false), mMessages (0), mRecordsLabel (0), mTotalRecordsLabel (0) { setWindowTitle (QString::fromUtf8((std::string("Opening ") + document->getSavePath().filename().string()).c_str())); @@ -25,26 +25,25 @@ CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) mLayout = new QVBoxLayout (this); - // file progress - mFile = new QLabel (this); + // total progress + mTotalRecordsLabel = new QLabel (this); - mLayout->addWidget (mFile); + mLayout->addWidget (mTotalRecordsLabel); - mFileProgress = new QProgressBar (this); + mTotalProgress = new QProgressBar (this); - mLayout->addWidget (mFileProgress); + mLayout->addWidget (mTotalProgress); - int size = static_cast (document->getContentFiles().size())+1; - if (document->isNew()) - --size; + mTotalProgress->setMinimum (0); + mTotalProgress->setMaximum (document->getData().getTotalRecords(document->getContentFiles())); + mTotalProgress->setTextVisible (true); + mTotalProgress->setValue (0); + mTotalRecords = 0; - mFileProgress->setMinimum (0); - mFileProgress->setMaximum (size); - mFileProgress->setTextVisible (true); - mFileProgress->setValue (0); + mFilesLoaded = 0; // record progress - mLayout->addWidget (mRecords = new QLabel ("Records", this)); + mLayout->addWidget (mRecordsLabel = new QLabel ("Records", this)); mRecordProgress = new QProgressBar (this); @@ -74,29 +73,32 @@ CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) connect (mButtons, SIGNAL (rejected()), this, SLOT (cancel())); } -void CSVDoc::LoadingDocument::nextStage (const std::string& name, int totalRecords) +void CSVDoc::LoadingDocument::nextStage (const std::string& name, int fileRecords) { - mFile->setText (QString::fromUtf8 (("Loading: " + name).c_str())); + ++mFilesLoaded; + size_t numFiles = mDocument->getContentFiles().size(); - mFileProgress->setValue (mFileProgress->value()+1); + mTotalRecordsLabel->setText (QString::fromUtf8 (("Loading: "+name + +" ("+std::to_string(mFilesLoaded)+" of "+std::to_string((numFiles))+")").c_str())); + + mTotalRecords = mTotalProgress->value(); mRecordProgress->setValue (0); - mRecordProgress->setMaximum (totalRecords>0 ? totalRecords : 1); + mRecordProgress->setMaximum (fileRecords>0 ? fileRecords : 1); - mTotalRecords = totalRecords; + mRecords = fileRecords; } void CSVDoc::LoadingDocument::nextRecord (int records) { - if (records<=mTotalRecords) + if (records <= mRecords) { - mRecordProgress->setValue (records); - - std::ostringstream stream; + mTotalProgress->setValue (mTotalRecords+records); - stream << "Records: " << records << " of " << mTotalRecords; + mRecordProgress->setValue(records); - mRecords->setText (QString::fromUtf8 (stream.str().c_str())); + mRecordsLabel->setText(QString::fromStdString( + "Records: "+std::to_string(records)+" of "+std::to_string(mRecords))); } } @@ -176,12 +178,12 @@ void CSVDoc::Loader::loadingStopped (CSMDoc::Document *document, bool completed, } void CSVDoc::Loader::nextStage (CSMDoc::Document *document, const std::string& name, - int totalRecords) + int fileRecords) { std::map::iterator iter = mDocuments.find (document); if (iter!=mDocuments.end()) - iter->second->nextStage (name, totalRecords); + iter->second->nextStage (name, fileRecords); } void CSVDoc::Loader::nextRecord (CSMDoc::Document *document, int records) diff --git a/apps/opencs/view/doc/loader.hpp b/apps/opencs/view/doc/loader.hpp index e004007c9..bda442cf1 100644 --- a/apps/opencs/view/doc/loader.hpp +++ b/apps/opencs/view/doc/loader.hpp @@ -25,16 +25,18 @@ namespace CSVDoc Q_OBJECT CSMDoc::Document *mDocument; - QLabel *mFile; - QLabel *mRecords; - QProgressBar *mFileProgress; + QLabel *mTotalRecordsLabel; + QLabel *mRecordsLabel; + QProgressBar *mTotalProgress; QProgressBar *mRecordProgress; bool mAborted; QDialogButtonBox *mButtons; QLabel *mError; QListWidget *mMessages; QVBoxLayout *mLayout; + int mRecords; int mTotalRecords; + int mFilesLoaded; private: From a967418a5a9468d0b994b55b354bda70ceed9886 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 6 Dec 2015 19:50:26 +1100 Subject: [PATCH 335/441] Fix Preview and Unpaged World subviews not rendering. Not sure if this is related to a later version of Ogre being used. --- apps/opencs/view/render/scenewidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 55cf039fc..832ccc70e 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -74,6 +74,8 @@ namespace CSVRender /// \todo make shortcut configurable QShortcut *focusToolbar = new QShortcut (Qt::Key_T, this, 0, 0, Qt::WidgetWithChildrenShortcut); connect (focusToolbar, SIGNAL (activated()), this, SIGNAL (focusToolbarRequest())); + + updateOgreWindow(); } CSVWidget::SceneToolMode *SceneWidget::makeLightingSelector (CSVWidget::SceneToolbar *parent) From 68e16b6cee0f533027e1d8f1293b8a5582ac5a68 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 6 Dec 2015 21:19:06 +1100 Subject: [PATCH 336/441] Convert RefNum index map to use find(). --- apps/opencs/model/world/data.hpp | 2 +- apps/opencs/model/world/refcollection.cpp | 15 +++++---------- apps/opencs/model/world/refcollection.hpp | 2 +- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index d59b83607..88a431fb7 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -104,7 +104,7 @@ namespace CSMWorld const ESM::Dialogue *mDialogue; // last loaded dialogue bool mBase; bool mProject; - std::map > mRefLoadCache; + std::map > mRefLoadCache; int mReaderIndex; std::vector > mReaders; diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 117aeb012..f811131a1 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -12,7 +12,7 @@ #include "record.hpp" void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base, - std::map& cache, CSMDoc::Messages& messages) + std::map& cache, CSMDoc::Messages& messages) { Record cell = mCells.getRecord (cellIndex); @@ -71,13 +71,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool else ref.mCell = cell2.mId; - // ignore content file number - std::map::iterator iter = cache.begin(); - for (; iter != cache.end(); ++iter) - { - if (ref.mRefNum.mIndex == iter->first.mIndex) - break; - } + std::map::iterator iter = cache.find (ref.mRefNum.mIndex); if (isDeleted) { @@ -114,11 +108,12 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool std::unique_ptr > record(new Record); record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + + cache.insert (std::make_pair (ref.mRefNum.mIndex, ref.mId)); + (base ? record->mBase : record->mModified) = std::move(ref); appendRecord(std::move(record)); - - cache.insert (std::make_pair (ref.mRefNum, ref.mId)); } else { diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index d031398d3..42bb36357 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -27,7 +27,7 @@ namespace CSMWorld {} void load (ESM::ESMReader& reader, int cellIndex, bool base, - std::map& cache, CSMDoc::Messages& messages); + std::map& cache, CSMDoc::Messages& messages); ///< Load a sequence of references. std::string getNewId(); From 306bfcbdf21a0292775b644e57a0c1f8606d7226 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 8 Dec 2015 00:27:20 +1100 Subject: [PATCH 337/441] Fix exception when deleting a record while dialogue subview for that record is open. --- apps/opencs/model/world/idtable.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index ec666b3df..ec492a91f 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -163,7 +163,11 @@ void CSMWorld::IdTable::cloneRecord(const std::string& origin, ///This method can return only indexes to the top level table cells QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const { - return index(mIdCollection->getIndex (id), column); + int idx = mIdCollection->searchId (id); + if (idx != -1) + return index(mIdCollection->getIndex (id), column); + else + return QModelIndex(); } void CSMWorld::IdTable::setRecord (const std::string& id, From 0de223c637d0820a97417d84796c1bfeb1836fb8 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 11 Dec 2015 07:18:54 +1100 Subject: [PATCH 338/441] Fix bug (#3067) where content file number was being ignored while searching for already loaded cell references. - Introduced by commits 49884f54f7f00e1d4413b77eae3d6091043aa016 and 896ab44d1e919852aae03be9ecb71378f031b6f5. - Also see https://github.com/OpenMW/openmw/pull/557 --- apps/opencs/model/world/data.cpp | 29 +++++++++++++++++++++++ apps/opencs/model/world/data.hpp | 1 + apps/opencs/model/world/refcollection.cpp | 10 ++++---- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index d8916935d..64d317bd4 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -927,6 +927,35 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mReader->setIndex(mReaderIndex++); mReader->open (path.string()); + mLoadedFiles.push_back(path.filename().string()); + + // at this point mReader->mHeader.mMaster have been populated for the file being loaded + for (size_t f = 0; f < mReader->getGameFiles().size(); ++f) + { + ESM::Header::MasterData& m = const_cast(mReader->getGameFiles().at(f)); + + int index = -1; + for (size_t i = 0; i < mLoadedFiles.size()-1; ++i) // -1 to ignore the current file + { + if (Misc::StringUtils::ciEqual(m.name, mLoadedFiles.at(i))) + { + index = static_cast(i); + break; + } + } + + if (index == -1) + { + // Tried to load a parent file that has not been loaded yet. This is bad, + // the launcher should have taken care of this. + std::string fstring = "File " + mReader->getName() + " asks for parent file " + m.name + + ", but it has not been loaded yet. Please check your load order."; + mReader->fail(fstring); + } + + m.index = index; + } + mBase = base; mProject = project; diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 88a431fb7..d1c0f1f54 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -106,6 +106,7 @@ namespace CSMWorld bool mProject; std::map > mRefLoadCache; int mReaderIndex; + std::vector mLoadedFiles; std::vector > mReaders; diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index f811131a1..3542a30bd 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -71,7 +71,10 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool else ref.mCell = cell2.mId; - std::map::iterator iter = cache.find (ref.mRefNum.mIndex); + unsigned int refNum = (ref.mRefNum.mIndex & 0x00ffffff) | + (ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24; + + std::map::iterator iter = cache.find(refNum); if (isDeleted) { @@ -106,11 +109,10 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // new reference ref.mId = getNewId(); + cache.insert(std::make_pair(refNum, ref.mId)); + std::unique_ptr > record(new Record); record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - - cache.insert (std::make_pair (ref.mRefNum.mIndex, ref.mId)); - (base ? record->mBase : record->mModified) = std::move(ref); appendRecord(std::move(record)); From 86945d19128468ad987b5bf4332602d613d25d9f Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 11 Dec 2015 21:00:13 +1100 Subject: [PATCH 339/441] Convert the CellRef record index lookup maps to use integer keys rather than strings. - Morrowind load over 300,000 references, so even small inefficiencies add up to longer loading times. - std::map is used, but should try others, std::unordered_map or even std::vector --- apps/opencs/model/world/data.hpp | 2 +- apps/opencs/model/world/ref.cpp | 2 + apps/opencs/model/world/ref.hpp | 2 + apps/opencs/model/world/refcollection.cpp | 178 +++++++++++++++++++++- apps/opencs/model/world/refcollection.hpp | 35 ++++- 5 files changed, 211 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index d1c0f1f54..47046d9de 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -104,7 +104,7 @@ namespace CSMWorld const ESM::Dialogue *mDialogue; // last loaded dialogue bool mBase; bool mProject; - std::map > mRefLoadCache; + std::map > mRefLoadCache; int mReaderIndex; std::vector mLoadedFiles; diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index 8c6f1d836..dbd9ae93d 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -3,6 +3,7 @@ #include CSMWorld::CellRef::CellRef() + : mIdNum(0) { mId.clear(); mCell.clear(); @@ -22,6 +23,7 @@ CSMWorld::CellRef& CSMWorld::CellRef::operator= (CSMWorld::CellRef&& other) if (this != &other) { ESM::CellRef::operator= (other); + mIdNum = other.mIdNum; mId = std::move(other.mId); mCell = std::move(other.mCell); mOriginalCell = std::move(other.mOriginalCell); diff --git a/apps/opencs/model/world/ref.hpp b/apps/opencs/model/world/ref.hpp index c439e7ca5..f9af5705f 100644 --- a/apps/opencs/model/world/ref.hpp +++ b/apps/opencs/model/world/ref.hpp @@ -10,6 +10,8 @@ namespace CSMWorld /// \brief Wrapper for CellRef sub record struct CellRef : public ESM::CellRef { + unsigned int mIdNum; + std::string mId; std::string mCell; std::string mOriginalCell; diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 3542a30bd..26ec507db 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -11,8 +11,37 @@ #include "universalid.hpp" #include "record.hpp" +namespace CSMWorld +{ + template<> + void Collection >::removeRows (int index, int count) + { + mRecords.erase(mRecords.begin()+index, mRecords.begin()+index+count); + + // index map is updated in RefCollection::removeRows() + } + + template<> + void Collection >::insertRecord (std::unique_ptr record, int index, + UniversalId::Type type) + { + int size = static_cast(mRecords.size()); + if (index < 0 || index > size) + throw std::runtime_error("index out of range"); + + std::unique_ptr > record2(static_cast*>(record.release())); + + if (index == size) + mRecords.push_back(std::move(record2)); + else + mRecords.insert(mRecords.begin()+index, std::move(record2)); + + // index map is updated in RefCollection::insertRecord() + } +} + void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base, - std::map& cache, CSMDoc::Messages& messages) + std::map& cache, CSMDoc::Messages& messages) { Record cell = mCells.getRecord (cellIndex); @@ -74,7 +103,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool unsigned int refNum = (ref.mRefNum.mIndex & 0x00ffffff) | (ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24; - std::map::iterator iter = cache.find(refNum); + std::map::iterator iter = cache.find(refNum); if (isDeleted) { @@ -83,7 +112,11 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex)); - messages.add (id, "Attempt to delete a non-existing reference"); + messages.add (id, "Attempt to delete a non-existing reference - RefNum index " + + std::to_string(ref.mRefNum.mIndex) + ", refID " + ref.mRefID + ", content file index " + + std::to_string(ref.mRefNum.mContentFile), + /*hint*/"", + CSMDoc::Message::Severity_Warning); continue; } @@ -107,9 +140,10 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool if (iter==cache.end()) { // new reference + ref.mIdNum = mNextId; // FIXME: fragile ref.mId = getNewId(); - cache.insert(std::make_pair(refNum, ref.mId)); + cache.insert(std::make_pair(refNum, ref.mIdNum)); std::unique_ptr > record(new Record); record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; @@ -120,9 +154,27 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool else { // old reference -> merge - ref.mId = iter->second; + int index = getIndex(iter->second); +#if 0 + // ref.mRefNum.mIndex : the key + // iter->second : previously cached idNum for the key + // index : position of the record for that idNum + // getRecord(index).get() : record in the index position + assert(iter->second != getRecord(index).get().mIdNum); // sanity check - int index = getIndex (ref.mId); + // check if the plugin used the same RefNum index for a different record + if (ref.mRefID != getRecord(index).get().mRefID) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex)); + messages.add(id, + "RefNum renamed from RefID \"" + getRecord(index).get().mRefID + "\" to \"" + + ref.mRefID + "\" (RefNum index " + std::to_string(ref.mRefNum.mIndex) + ")", + /*hint*/"", + CSMDoc::Message::Severity_Info); + } +#endif + ref.mId = getRecord(index).get().mId; + ref.mIdNum = extractIdNum(ref.mId); std::unique_ptr > record(new Record(getRecord(index))); record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; @@ -139,3 +191,117 @@ std::string CSMWorld::RefCollection::getNewId() stream << "ref#" << mNextId++; return stream.str(); } + +unsigned int CSMWorld::RefCollection::extractIdNum (const std::string& id) const +{ + std::string::size_type separator = id.find_last_of('#'); + + if (separator == std::string::npos) + throw std::runtime_error("invalid ref ID: " + id); + + return static_cast(std::stoi(id.substr(separator+1))); +} + +int CSMWorld::RefCollection::getIndex (unsigned int id) const +{ + int index = searchId(id); + + if (index == -1) + throw std::runtime_error("invalid RefNum: " + std::to_string(id)); + + return index; +} + +int CSMWorld::RefCollection::searchId (unsigned int id) const +{ + std::map::const_iterator iter = mRefIndex.find(id); + + if (iter == mRefIndex.end()) + return -1; + + return iter->second; +} + +void CSMWorld::RefCollection::removeRows (int index, int count) +{ + Collection >::removeRows(index, count); // erase records only + + std::map::iterator iter = mRefIndex.begin(); + while (iter != mRefIndex.end()) + { + if (iter->second>=index) + { + if (iter->second >= index+count) + { + iter->second -= count; + ++iter; + } + else + mRefIndex.erase(iter++); + } + else + ++iter; + } +} + +void CSMWorld::RefCollection::appendBlankRecord (const std::string& id, UniversalId::Type type) +{ + std::unique_ptr > record2(new Record); + + record2->mState = Record::State_ModifiedOnly; + record2->mModified.blank(); + + record2->get().mId = id; + record2->get().mIdNum = extractIdNum(id); + + Collection >::appendRecord(std::move(record2)); +} + +void CSMWorld::RefCollection::cloneRecord (const std::string& origin, + const std::string& destination, + const UniversalId::Type type) +{ + std::unique_ptr > copy(new Record); + + copy->mModified = getRecord(origin).get(); + copy->mState = RecordBase::State_ModifiedOnly; + + copy->get().mId = destination; + copy->get().mIdNum = extractIdNum(destination); + + insertRecord(std::move(copy), getAppendIndex(destination, type)); // call RefCollection::insertRecord() +} + +int CSMWorld::RefCollection::searchId (const std::string& id) const +{ + return searchId(extractIdNum(id)); +} + +void CSMWorld::RefCollection::appendRecord (std::unique_ptr record, UniversalId::Type type) +{ + int index = getAppendIndex(/*id*/"", type); // for CellRef records id is ignored + + mRefIndex.insert(std::make_pair(static_cast*>(record.get())->get().mIdNum, index)); + + Collection >::insertRecord(std::move(record), index, type); // add records only +} + +void CSMWorld::RefCollection::insertRecord (std::unique_ptr record, int index, + UniversalId::Type type) +{ + int size = getAppendIndex(/*id*/"", type); // for CellRef records id is ignored + unsigned int idNum = static_cast*>(record.get())->get().mIdNum; + + Collection >::insertRecord(std::move(record), index, type); // add records only + + if (index < size-1) + { + for (std::map::iterator iter(mRefIndex.begin()); iter != mRefIndex.end(); ++iter) + { + if (iter->second >= index) + ++(iter->second); + } + } + + mRefIndex.insert(std::make_pair(idNum, index)); +} diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index 42bb36357..6b4704bc9 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -14,12 +14,27 @@ namespace CSMWorld struct Cell; class UniversalId; + template<> + void Collection >::removeRows (int index, int count); + + template<> + void Collection >::insertRecord (std::unique_ptr record, int index, + UniversalId::Type type); + /// \brief References in cells class RefCollection : public Collection { Collection& mCells; + std::map mRefIndex; + int mNextId; + unsigned int extractIdNum(const std::string& id) const; + + int getIndex (unsigned int id) const; + + int searchId (unsigned int id) const; + public: // MSVC needs the constructor for a class inheriting a template to be defined in header RefCollection (Collection& cells) @@ -27,10 +42,28 @@ namespace CSMWorld {} void load (ESM::ESMReader& reader, int cellIndex, bool base, - std::map& cache, CSMDoc::Messages& messages); + std::map& cache, CSMDoc::Messages& messages); ///< Load a sequence of references. std::string getNewId(); + + virtual void removeRows (int index, int count); + + virtual void appendBlankRecord (const std::string& id, + UniversalId::Type type = UniversalId::Type_None); + + virtual void cloneRecord (const std::string& origin, + const std::string& destination, + const UniversalId::Type type); + + virtual int searchId (const std::string& id) const; + + virtual void appendRecord (std::unique_ptr record, + UniversalId::Type type = UniversalId::Type_None); + + virtual void insertRecord (std::unique_ptr record, + int index, + UniversalId::Type type = UniversalId::Type_None); }; } From 47b5fa9dae72c35546fd172c9e06929c6ec39042 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 11 Dec 2015 22:18:17 +1100 Subject: [PATCH 340/441] Convert some of ostringstream << operations to std::to_sting() calls. - At least with MSVC the latter is more efficient. - There are many more, but leave them until they show up during profiling (so far only loading was profiled, and mainly cell references) --- apps/opencs/model/world/refcollection.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 26ec507db..607e5690d 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,6 +1,5 @@ #include "refcollection.hpp" -#include #include #include @@ -63,10 +62,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // ignoring moved references sub-record; instead calculate cell from coordinates std::pair index = ref.getCellIndex(); - std::ostringstream stream; - stream << "#" << index.first << " " << index.second; - - ref.mCell = stream.str(); + ref.mCell = "#" + std::to_string(index.first) + " " + std::to_string(index.second); if (!base && // don't try to update base records mref.mRefNum.mIndex != 0) // MVRF tag found @@ -91,9 +87,8 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool std::cerr << "Position: #" << index.first << " " << index.second <<", Target #"<< mref.mTarget[0] << " " << mref.mTarget[1] << std::endl; - std::ostringstream stream2; - stream2 << "#" << mref.mTarget[0] << " " << mref.mTarget[1]; - ref.mCell = stream2.str(); // overwrite + // overwrite + ref.mCell = "#" + std::to_string(mref.mTarget[0]) + " " + std::to_string(mref.mTarget[1]); } } } @@ -187,9 +182,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool std::string CSMWorld::RefCollection::getNewId() { - std::ostringstream stream; - stream << "ref#" << mNextId++; - return stream.str(); + return "ref#" + std::to_string(mNextId++); } unsigned int CSMWorld::RefCollection::extractIdNum (const std::string& id) const From de5ce7059e5916f29be7ebce16f47e7fe4986335 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 11 Dec 2015 22:20:21 +1100 Subject: [PATCH 341/441] Use load message system for moved ref target cell error logging. --- apps/opencs/model/world/refcollection.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 607e5690d..0f894dc87 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,7 +1,5 @@ #include "refcollection.hpp" -#include - #include #include @@ -82,10 +80,13 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // message if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1]) { - std::cerr << "The Position of moved ref " - << ref.mRefID << " does not match the target cell" << std::endl; - std::cerr << "Position: #" << index.first << " " << index.second - <<", Target #"<< mref.mTarget[0] << " " << mref.mTarget[1] << std::endl; + CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex)); + messages.add (id, "The Position of moved ref " + ref.mRefID + " (#" + + std::to_string(index.first) + " " + std::to_string(index.second) + + ") does not match the target cell (#" + + std::to_string(mref.mTarget[0]) + " " + std::to_string(mref.mTarget[1]) + ")", + /*hint*/"", + CSMDoc::Message::Severity_Warning); // overwrite ref.mCell = "#" + std::to_string(mref.mTarget[0]) + " " + std::to_string(mref.mTarget[1]); From f6d6138f4659ee4a016070abd53b6eb1103fc372 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 11 Dec 2015 22:39:03 +1100 Subject: [PATCH 342/441] Minor formatting change. --- apps/opencs/model/world/refcollection.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 0f894dc87..25c934c7f 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -40,7 +40,7 @@ namespace CSMWorld void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base, std::map& cache, CSMDoc::Messages& messages) { - Record cell = mCells.getRecord (cellIndex); + Record cell = mCells.getRecord(cellIndex); Cell& cell2 = base ? cell.mBase : cell.mModified; @@ -80,8 +80,8 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // message if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1]) { - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex)); - messages.add (id, "The Position of moved ref " + ref.mRefID + " (#" + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex)); + messages.add(id, "The Position of moved ref " + ref.mRefID + " (#" + std::to_string(index.first) + " " + std::to_string(index.second) + ") does not match the target cell (#" + std::to_string(mref.mTarget[0]) + " " + std::to_string(mref.mTarget[1]) + ")", @@ -103,12 +103,10 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool if (isDeleted) { - if (iter==cache.end()) + if (iter == cache.end()) { - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, - mCells.getId (cellIndex)); - - messages.add (id, "Attempt to delete a non-existing reference - RefNum index " + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex)); + messages.add(id, "Attempt to delete a non-existing reference - RefNum index " + std::to_string(ref.mRefNum.mIndex) + ", refID " + ref.mRefID + ", content file index " + std::to_string(ref.mRefNum.mContentFile), /*hint*/"", @@ -116,12 +114,12 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool continue; } - int index = getIndex (iter->second); + int index = getIndex(iter->second); if (base) { - removeRows (index, 1); - cache.erase (iter); + removeRows(index, 1); + cache.erase(iter); } else { @@ -133,7 +131,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool continue; } - if (iter==cache.end()) + if (iter == cache.end()) { // new reference ref.mIdNum = mNextId; // FIXME: fragile From 477e0ee912952a1e3b5c23534af826c0e2d9f636 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 18 Dec 2015 19:39:40 +1100 Subject: [PATCH 343/441] Convert std::to_string() calls to snprintf() for cell references. - Profiling indicates snprintf() is more efficient when using MSVC (not tested with linux) --- apps/opencs/model/world/refcollection.cpp | 16 ++++++++++-- libs/platform/strings.h | 31 +++++++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 25c934c7f..5d128a357 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "ref.hpp" #include "cell.hpp" #include "universalid.hpp" @@ -60,7 +62,12 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // ignoring moved references sub-record; instead calculate cell from coordinates std::pair index = ref.getCellIndex(); - ref.mCell = "#" + std::to_string(index.first) + " " + std::to_string(index.second); + char buf[100]; + int res = snprintf(buf, 100, "#%d %d", index.first, index.second); + if (res > 0 && res < 100) + ref.mCell = std::string(buf); + else + throw std::runtime_error("getNewId possible buffer overflow"); if (!base && // don't try to update base records mref.mRefNum.mIndex != 0) // MVRF tag found @@ -181,7 +188,12 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool std::string CSMWorld::RefCollection::getNewId() { - return "ref#" + std::to_string(mNextId++); + char buf[100]; + int res = snprintf(buf, 100, "ref#%d", mNextId++); + if (res > 0 && res < 100) + return std::string(buf); + else + throw std::runtime_error("getNewId possible buffer overflow"); } unsigned int CSMWorld::RefCollection::extractIdNum (const std::string& id) const diff --git a/libs/platform/strings.h b/libs/platform/strings.h index 305705044..027f90333 100644 --- a/libs/platform/strings.h +++ b/libs/platform/strings.h @@ -10,11 +10,38 @@ # pragma warning(disable: 4996) # define strcasecmp stricmp # if (_MSC_VER < 1900) -# define snprintf _snprintf +# include +# include +# define snprintf c99_snprintf +# define vsnprintf c99_vsnprintf +/* see http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 */ +inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = c99_vsnprintf(outBuf, size, format, ap); + va_end(ap); + + return count; +} # endif #else # warning "Unable to determine your compiler, you should probably take a look here." # include // Just take a guess -#endif +#endif #endif /* _STRINGS_WRAPPER_H */ From 76e9a0359669371bdf06936224a2f7668104f3b5 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 18 Dec 2015 19:41:19 +1100 Subject: [PATCH 344/441] More efficient implementation of an earlier fix so that id is searched only once - see commit 306bfcbdf21a0292775b644e57a0c1f8606d7226 --- apps/opencs/model/world/idtable.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index ec492a91f..6365a78c1 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -163,9 +163,9 @@ void CSMWorld::IdTable::cloneRecord(const std::string& origin, ///This method can return only indexes to the top level table cells QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const { - int idx = mIdCollection->searchId (id); - if (idx != -1) - return index(mIdCollection->getIndex (id), column); + int row = mIdCollection->searchId (id); + if (row != -1) + return index(row, column); else return QModelIndex(); } From 226f7b69281b84d78d6aaef2be0ce7ef03d518dd Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 18 Dec 2015 19:42:19 +1100 Subject: [PATCH 345/441] Suppress additional MSVC warnings after Update 1 --- CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4395df940..e8e0a26f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -706,7 +706,13 @@ if (WIN32) # MSVC 2015 if (MSVC_VERSION GREATER 1899) - set(WARNINGS_DISABLE ${WARNINGS_DISABLE} 5026 5027) + set(WARNINGS_DISABLE ${WARNINGS_DISABLE} + 4464 # relative include path contains '..' + 5026 # move constructor was implicitly defined as deleted + 5027 # move assignment operator was implicitly defined as deleted + 5031 # #pragma warning(pop): likely mismatch, popping warning state pushed in different file + 5032 # detected #pragma warning(push) with no corresponding #pragma warning(pop) + ) endif() foreach(d ${WARNINGS_DISABLE}) From edf4e6ff954f6ee135c0d2c1a9955f7fca90e231 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 19 Dec 2015 17:22:09 +1100 Subject: [PATCH 346/441] Fix crash when repeatedly performing delete/undo due to empty moved record. - introduced in commit 23e7e3c165bb2631f9d8eb298f86da862e91cefa --- apps/opencs/model/world/commands.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 0f6681ba1..ccb41372c 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -144,6 +144,7 @@ void CSMWorld::RevertCommand::redo() if (state==RecordBase::State_ModifiedOnly) { + mOld = std::move(mModel.getRecord (mId).clone()); mModel.removeRows (index.row(), 1); } else @@ -179,6 +180,7 @@ void CSMWorld::DeleteCommand::redo() if (state==RecordBase::State_ModifiedOnly) { + mOld = std::move(mModel.getRecord (mId).clone()); mModel.removeRows (index.row(), 1); } else From 06f9922822bf5a076894bce44bde37234d7ccee1 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 19 Dec 2015 17:30:55 +1100 Subject: [PATCH 347/441] Performance improvements for loading Info records. - The order of info records with the same topic are maintained in Collection::mRecords - The index lookup data structure are not ordered. The topic string is hashed. The infos for the topic are simply placed in a vector. - The index values for appending or inserting a record takes prev/next values (if exist) - FIXME: prev/next values are not adjusted for adding or removing records - FIXME: undo after reordering does not reset the modified flag --- CMakeLists.txt | 1 + apps/opencs/CMakeLists.txt | 1 + apps/opencs/model/world/collection.hpp | 8 - apps/opencs/model/world/collectionbase.cpp | 5 + apps/opencs/model/world/collectionbase.hpp | 6 + apps/opencs/model/world/idtable.cpp | 8 +- apps/opencs/model/world/infocollection.cpp | 337 +++++++++---- apps/opencs/model/world/infocollection.hpp | 76 ++- extern/murmurhash/CMakeLists.txt | 17 + extern/murmurhash/MurmurHash2.cpp | 522 +++++++++++++++++++++ extern/murmurhash/MurmurHash2.h | 38 ++ 11 files changed, 929 insertions(+), 90 deletions(-) create mode 100644 extern/murmurhash/CMakeLists.txt create mode 100644 extern/murmurhash/MurmurHash2.cpp create mode 100644 extern/murmurhash/MurmurHash2.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e8e0a26f7..ff5191c46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -576,6 +576,7 @@ add_subdirectory (extern/shiny) add_subdirectory (extern/ogre-ffmpeg-videoplayer) add_subdirectory (extern/oics) add_subdirectory (extern/sdl4ogre) +add_subdirectory (extern/murmurhash) # Components add_subdirectory (components) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 30c78e710..7f3709b8a 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -211,6 +211,7 @@ target_link_libraries(openmw-cs ${OGRE_Overlay_LIBRARIES} ${OGRE_STATIC_PLUGINS} ${SHINY_LIBRARIES} + ${MURMURHASH_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 475a26263..594707cf2 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -60,8 +60,6 @@ namespace CSMWorld protected: - const std::map& getIdMap() const; - const std::vector > >& getRecords() const; bool reorderRowsImp (int baseIndex, const std::vector& newOrder); @@ -159,12 +157,6 @@ namespace CSMWorld NestableColumn *getNestableColumn (int column) const; }; - template - const std::map& Collection::getIdMap() const - { - return mIndex; - } - template const std::vector > >& Collection::getRecords() const { diff --git a/apps/opencs/model/world/collectionbase.cpp b/apps/opencs/model/world/collectionbase.cpp index 6134dc172..f20fc643e 100644 --- a/apps/opencs/model/world/collectionbase.cpp +++ b/apps/opencs/model/world/collectionbase.cpp @@ -8,6 +8,11 @@ CSMWorld::CollectionBase::CollectionBase() {} CSMWorld::CollectionBase::~CollectionBase() {} +int CSMWorld::CollectionBase::getInsertIndex (const std::string& id, UniversalId::Type type, RecordBase *record) const +{ + return getAppendIndex(id, type); +} + int CSMWorld::CollectionBase::searchColumnIndex (Columns::ColumnId id) const { int columns = getColumns(); diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp index fa0399e49..ac047de27 100644 --- a/apps/opencs/model/world/collectionbase.hpp +++ b/apps/opencs/model/world/collectionbase.hpp @@ -98,6 +98,12 @@ namespace CSMWorld /// /// \return Success? + virtual int getInsertIndex (const std::string& id, + UniversalId::Type type = UniversalId::Type_None, + RecordBase *record = nullptr) const; + ///< Works like getAppendIndex unless an overloaded method uses the record pointer + /// to get additional info about the record that results in an alternative index. + int searchColumnIndex (Columns::ColumnId id) const; ///< Return index of column with the given \a id. If no such column exists, -1 is returned. diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 6365a78c1..bb3bb1837 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -177,7 +177,13 @@ void CSMWorld::IdTable::setRecord (const std::string& id, if (index==-1) { - int index2 = mIdCollection->getAppendIndex (id, type); + // For info records, appendRecord may use a different index than the one returned by + // getAppendIndex (because of prev/next links). This can result in the display not + // updating correctly after an undo + // + // Use an alternative method to get the correct index. For non-Info records the + // record pointer is ignored and internally calls getAppendIndex. + int index2 = mIdCollection->getInsertIndex (id, type, record.get()); beginInsertRows (QModelIndex(), index2, index2); diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index c331a2d73..7b7c274a0 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -8,42 +8,79 @@ #include -void CSMWorld::InfoCollection::load (const Info& record, bool base) +namespace CSMWorld { - int index = searchId (record.mId); + template<> + void Collection >::removeRows (int index, int count) + { + mRecords.erase(mRecords.begin()+index, mRecords.begin()+index+count); + + // index map is updated in InfoCollection::removeRows() + } - if (index==-1) + template<> + void Collection >::insertRecord (std::unique_ptr record, + int index, UniversalId::Type type) { - // new record - std::unique_ptr > record2(new Record); - record2->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - (base ? record2->mBase : record2->mModified) = record; + int size = static_cast(mRecords.size()); + if (index < 0 || index > size) + throw std::runtime_error("index out of range"); + + std::unique_ptr > record2(static_cast*>(record.release())); - int index2 = -1; + if (index == size) + mRecords.push_back(std::move(record2)); + else + mRecords.insert(mRecords.begin()+index, std::move(record2)); - std::string topic = Misc::StringUtils::lowerCase (record2->get().mTopicId); + // index map is updated in InfoCollection::insertRecord() + } - if (!record2->get().mPrev.empty()) + template<> + bool Collection >::reorderRowsImp (int baseIndex, + const std::vector& newOrder) + { + if (!newOrder.empty()) { - index2 = getInfoIndex (record2->get().mPrev, topic); + int size = static_cast(newOrder.size()); - if (index2!=-1) - ++index2; - } + // check that all indices are present + std::vector test(newOrder); + std::sort(test.begin(), test.end()); + if (*test.begin() != 0 || *--test.end() != size-1) + return false; - if (index2==-1 && !record2->get().mNext.empty()) - { - index2 = getInfoIndex (record2->get().mNext, topic); - } + // reorder records + std::vector > > buffer(size); - if (index2==-1) - { - Range range = getTopicRange (topic); + // FIXME: BUG: undo does not remove modified flag + for (int i = 0; i < size; ++i) + { + buffer[newOrder[i]] = std::move(mRecords[baseIndex+i]); + buffer[newOrder[i]]->setModified(buffer[newOrder[i]]->get()); + } - index2 = std::distance (getRecords().begin(), range.second); + std::move(buffer.begin(), buffer.end(), mRecords.begin()+baseIndex); + + // index map is updated in InfoCollection::reorderRows() } - insertRecord (std::move(record2), index2); + return true; + } +} + +void CSMWorld::InfoCollection::load (const Info& record, bool base) +{ + int index = searchId(record.mId); + + if (index == -1) + { + // new record + std::unique_ptr > record2(new Record); + record2->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record2->mBase : record2->mModified) = record; + + appendRecord(std::move(record2)); } else { @@ -61,30 +98,74 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) int CSMWorld::InfoCollection::getInfoIndex (const std::string& id, const std::string& topic) const { - std::string fullId = Misc::StringUtils::lowerCase (topic) + "#" + id; + // find the topic first + std::map > >::const_iterator iter + = mInfoIndex.find(StringHash(std::make_shared(Misc::StringUtils::lowerCase(topic)))); - std::pair range = getTopicRange (topic); + if (iter == mInfoIndex.end()) + return -1; - for (; range.first!=range.second; ++range.first) - if (Misc::StringUtils::ciEqual((*range.first).get()->get().mId, fullId)) - return std::distance (getRecords().begin(), range.first); + // brute force loop + for (std::vector >::const_iterator it = iter->second.begin(); + it != iter->second.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->first, id)) + return it->second; + } return -1; } -int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const +// Calling insertRecord() using index from getInsertIndex() needs to take into account of +// prev/next records; an example is deleting a record then undo +int CSMWorld::InfoCollection::getInsertIndex (const std::string& id, + UniversalId::Type type, RecordBase *record) const { - std::string::size_type separator = id.find_last_of ('#'); + if (record == nullptr) + { + std::string::size_type separator = id.find_last_of('#'); + + if (separator == std::string::npos) + throw std::runtime_error("invalid info ID: " + id); + + std::pair range = getTopicRange(id.substr(0, separator)); + + if (range.first == range.second) + return Collection >::getAppendIndex(id, type); + + return std::distance(getRecords().begin(), range.second); + } + + int index = -1; + + const Info& info = static_cast*>(record)->get(); + std::string topic = info.mTopicId; + + // if the record has a prev, find its index value + if (!info.mPrev.empty()) + { + index = getInfoIndex(info.mPrev, topic); - if (separator==std::string::npos) - throw std::runtime_error ("invalid info ID: " + id); + if (index != -1) + ++index; // if prev exists, set current index to one above prev + } - std::pair range = getTopicRange (id.substr (0, separator)); + // if prev doesn't exist or not found and the record has a next, find its index value + if (index == -1 && !info.mNext.empty()) + { + // if next exists, use its index as the current index + index = getInfoIndex(info.mNext, topic); + } - if (range.first==range.second) - return Collection >::getAppendIndex (id, type); + // if next doesn't exist or not found (i.e. neither exist yet) then start a new one + if (index == -1) + { + Range range = getTopicRange(topic); // getTopicRange converts topic to lower case first + + index = std::distance(getRecords().begin(), range.second); + } - return std::distance (getRecords().begin(), range.second); + return index; } bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector& newOrder) @@ -101,7 +182,23 @@ bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector >::reorderRowsImp(baseIndex, newOrder)) + return false; + + // adjust index + int size = static_cast(newOrder.size()); + for (std::map > >::iterator iter + = mInfoIndex.begin(); iter != mInfoIndex.end(); ++iter) + { + for (std::vector >::iterator it = iter->second.begin(); + it != iter->second.end(); ++it) + { + if (it->second >= baseIndex && it->second < baseIndex+size) + it->second = newOrder.at(it->second-baseIndex)+baseIndex; + } + } + + return true; } void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) @@ -116,7 +213,7 @@ void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ES { int index = searchId (id); - if (index==-1) + if (index == -1) { // deleting a record that does not exist // ignore it for now @@ -144,74 +241,54 @@ void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ES CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const std::string& topic) const { - std::string topic2 = Misc::StringUtils::lowerCase (topic); + std::string lowerTopic = Misc::StringUtils::lowerCase (topic); - std::map::const_iterator iter = getIdMap().lower_bound (topic2); + // find the topic + std::map > >::const_iterator iter + = mInfoIndex.find(StringHash(std::make_shared(lowerTopic))); - // Skip invalid records: The beginning of a topic string could be identical to another topic - // string. - for (; iter!=getIdMap().end(); ++iter) - { - std::string testTopicId = - Misc::StringUtils::lowerCase (getRecord (iter->second).get().mTopicId); - - if (testTopicId==topic2) - break; - - std::size_t size = topic2.size(); - - if (testTopicId.size()second; - - while (begin != getRecords().begin()) + // topic found, find the starting index + int low = INT_MAX; + for (std::vector >::const_iterator it = iter->second.begin(); + it != iter->second.end(); ++it) { - if (!Misc::StringUtils::ciEqual((*begin)->get().mTopicId, topic2)) - { - // we've gone one too far, go back - ++begin; - break; - } - --begin; + low = std::min(low, it->second); } - // Find end - RecordConstIterator end = begin; + RecordConstIterator begin = getRecords().begin() + low; - for (; end!=getRecords().end(); ++end) - if (!Misc::StringUtils::ciEqual((*end)->get().mTopicId, topic2)) - break; + // Find end (one past the range) + RecordConstIterator end = begin + iter->second.size(); + if (end != getRecords().end()) + ++end; return Range (begin, end); } void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId) { - std::string id = Misc::StringUtils::lowerCase(dialogueId); std::vector erasedRecords; - std::map::const_iterator current = getIdMap().lower_bound(id); - std::map::const_iterator end = getIdMap().end(); - for (; current != end; ++current) + Range range = getTopicRange(dialogueId); // getTopicRange converts dialogueId to lower case first + + for (; range.first != range.second; ++range.first) { - const Record& record = getRecord(current->second); + const Record& record = **range.first; if (Misc::StringUtils::ciEqual(dialogueId, record.get().mTopicId)) { if (record.mState == RecordBase::State_ModifiedOnly) { - erasedRecords.push_back(current->second); + erasedRecords.push_back(range.first - getRecords().begin()); } else { std::unique_ptr > record2(new Record(record)); record2->mState = RecordBase::State_Deleted; - setRecord(current->second, std::move(record2)); + setRecord(range.first - getRecords().begin(), std::move(record2)); } } else @@ -226,3 +303,105 @@ void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId erasedRecords.pop_back(); } } + +// FIXME: removing a record should adjust prev/next and mark those records as modified +// accordingly (also consider undo) +void CSMWorld::InfoCollection::removeRows (int index, int count) +{ + Collection >::removeRows(index, count); // erase records only + + for (std::map > >::iterator iter + = mInfoIndex.begin(); iter != mInfoIndex.end();) + { + for (std::vector >::iterator it = iter->second.begin(); + it != iter->second.end();) + { + if (it->second >= index) + { + if (it->second >= index+count) + { + it->second -= count; + ++it; + } + else + iter->second.erase(it); + } + else + ++it; + } + + // check for an empty vector + if (iter->second.empty()) + mInfoIndex.erase(iter++); + else + ++iter; + } +} + +void CSMWorld::InfoCollection::appendBlankRecord (const std::string& id, UniversalId::Type type) +{ + std::unique_ptr > record2(new Record); + + record2->mState = Record::State_ModifiedOnly; + record2->mModified.blank(); + + record2->get().mId = id; + + insertRecord(std::move(record2), getInsertIndex(id, type, nullptr), type); // call InfoCollection::insertRecord() +} + +int CSMWorld::InfoCollection::searchId (const std::string& id) const +{ + std::string::size_type separator = id.find_last_of('#'); + + if (separator == std::string::npos) + throw std::runtime_error("invalid info ID: " + id); + + return getInfoIndex(id.substr(separator+1), id.substr(0, separator)); +} + +void CSMWorld::InfoCollection::appendRecord (std::unique_ptr record, UniversalId::Type type) +{ + int index = getInsertIndex(static_cast*>(record.get())->get().mId, type, record.get()); + + insertRecord(std::move(record), index, type); +} + +void CSMWorld::InfoCollection::insertRecord (std::unique_ptr record, int index, + UniversalId::Type type) +{ + int size = static_cast(getRecords().size()); + + std::string id = static_cast*>(record.get())->get().mId; + std::string::size_type separator = id.find_last_of('#'); + + if (separator == std::string::npos) + throw std::runtime_error("invalid info ID: " + id); + + Collection >::insertRecord(std::move(record), index, type); // add records only + + // adjust index + if (index < size-1) + { + for (std::map > >::iterator iter + = mInfoIndex.begin(); iter != mInfoIndex.end(); ++iter) + { + for (std::vector >::iterator it = iter->second.begin(); + it != iter->second.end(); ++it) + { + if (it->second >= index) + ++(it->second); + } + } + } + + // get iterator for existing topic or a new topic + std::string lowerId = Misc::StringUtils::lowerCase(id); + std::pair > >::iterator, bool> res + = mInfoIndex.insert( + std::make_pair(StringHash(std::make_shared(lowerId.substr(0, separator))), + std::vector >())); // empty vector + + // insert info and index + res.first->second.push_back(std::make_pair(lowerId.substr(separator+1), index)); +} diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index da389763c..73fbaf9ef 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -1,6 +1,8 @@ #ifndef CSM_WOLRD_INFOCOLLECTION_H #define CSM_WOLRD_INFOCOLLECTION_H +#include + #include "collection.hpp" #include "info.hpp" @@ -11,6 +13,48 @@ namespace ESM namespace CSMWorld { + struct StringHash + { + uint64_t mHash; + std::shared_ptr mString; + + StringHash (std::shared_ptr str) : mString(str) + { + mHash = MurmurHash64A(str->c_str(), str->size(), /*seed*/1); + } + }; +} + +namespace std +{ + template<> struct less + { + bool operator() (const CSMWorld::StringHash& lhs, const CSMWorld::StringHash& rhs) const + { + if (lhs.mHash < rhs.mHash) + return true; + + if (lhs.mHash > rhs.mHash) + return false; + + return *lhs.mString < *rhs.mString; + } + }; +} + +namespace CSMWorld +{ + template<> + void Collection >::removeRows (int index, int count); + + template<> + void Collection >::insertRecord (std::unique_ptr record, + int index, UniversalId::Type type); + + template<> + bool Collection >::reorderRowsImp (int baseIndex, + const std::vector& newOrder); + class InfoCollection : public Collection > { public: @@ -20,18 +64,32 @@ namespace CSMWorld private: + // The general strategy is to keep the records in Collection kept in order (within + // a topic group) while the index lookup maps are not ordered. It is assumed that + // each topic has a small number of infos, which allows the use of vectors for + // iterating through them without too much penalty. + // + // NOTE: hashed topic string as well as id string are stored in lower case. + std::map > > mInfoIndex; + void load (const Info& record, bool base); int getInfoIndex (const std::string& id, const std::string& topic) const; ///< Return index for record \a id or -1 (if not present; deleted records are considered) /// /// \param id info ID without topic prefix + // + /// \attention id and topic are assumed to be in lower case public: - virtual int getAppendIndex (const std::string& id, - UniversalId::Type type = UniversalId::Type_None) const; + virtual int getInsertIndex (const std::string& id, + UniversalId::Type type = UniversalId::Type_None, + RecordBase *record = nullptr) const; ///< \param type Will be ignored, unless the collection supports multiple record types + /// + /// Works like getAppendIndex unless an overloaded method uses the record pointer + /// to get additional info about the record that results in an alternative index. virtual bool reorderRows (int baseIndex, const std::vector& newOrder); ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices @@ -46,6 +104,20 @@ namespace CSMWorld /// the given topic. void removeDialogueInfos(const std::string& dialogueId); + + virtual void removeRows (int index, int count); + + virtual void appendBlankRecord (const std::string& id, + UniversalId::Type type = UniversalId::Type_None); + + virtual int searchId (const std::string& id) const; + + virtual void appendRecord (std::unique_ptr record, + UniversalId::Type type = UniversalId::Type_None); + + virtual void insertRecord (std::unique_ptr record, + int index, + UniversalId::Type type = UniversalId::Type_None); }; } diff --git a/extern/murmurhash/CMakeLists.txt b/extern/murmurhash/CMakeLists.txt new file mode 100644 index 000000000..cd8199ff0 --- /dev/null +++ b/extern/murmurhash/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.8) + +# This is NOT intended as a stand-alone build system! Instead, you should include this from the main CMakeLists of your project. + +set(MURMURHASH_LIBRARY "murmurhash") + +# Sources +set(SOURCE_FILES + MurmurHash2.cpp +) + +add_library(${MURMURHASH_LIBRARY} STATIC ${SOURCE_FILES}) + +set(MURMURHASH_LIBRARIES ${MURMURHASH_LIBRARY}) + +link_directories(${CMAKE_CURRENT_BINARY_DIR}) +set(MURMURHASH_LIBRARIES ${MURMURHASH_LIBRARIES} PARENT_SCOPE) diff --git a/extern/murmurhash/MurmurHash2.cpp b/extern/murmurhash/MurmurHash2.cpp new file mode 100644 index 000000000..d1b6f476e --- /dev/null +++ b/extern/murmurhash/MurmurHash2.cpp @@ -0,0 +1,522 @@ +//----------------------------------------------------------------------------- +// MurmurHash2 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +// Note - This code makes a few assumptions about how your machine behaves - + +// 1. We can read a 4-byte value from any address without crashing +// 2. sizeof(int) == 4 + +// And it has a few limitations - + +// 1. It will not work incrementally. +// 2. It will not produce the same results on little-endian and big-endian +// machines. + +#include "MurmurHash2.h" + +//----------------------------------------------------------------------------- +// Platform-specific functions and macros + +// Microsoft Visual Studio + +#if defined(_MSC_VER) + +#define BIG_CONSTANT(x) (x) + +// Other compilers + +#else // defined(_MSC_VER) + +#define BIG_CONSTANT(x) (x##LLU) + +#endif // !defined(_MSC_VER) + +//----------------------------------------------------------------------------- + +uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ) +{ + // 'm' and 'r' are mixing constants generated offline. + // They're not really 'magic', they just happen to work well. + + const uint32_t m = 0x5bd1e995; + const int r = 24; + + // Initialize the hash to a 'random' value + + uint32_t h = seed ^ len; + + // Mix 4 bytes at a time into the hash + + const unsigned char * data = (const unsigned char *)key; + + while(len >= 4) + { + uint32_t k = *(uint32_t*)data; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + // Handle the last few bytes of the input array + + switch(len) + { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + + // Do a few final mixes of the hash to ensure the last few + // bytes are well-incorporated. + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} + +//----------------------------------------------------------------------------- +// MurmurHash2, 64-bit versions, by Austin Appleby + +// The same caveats as 32-bit MurmurHash2 apply here - beware of alignment +// and endian-ness issues if used across multiple platforms. + +// 64-bit hash for 64-bit platforms + +uint64_t MurmurHash64A ( const void * key, int len, uint64_t seed ) +{ + const uint64_t m = BIG_CONSTANT(0xc6a4a7935bd1e995); + const int r = 47; + + uint64_t h = seed ^ (len * m); + + const uint64_t * data = (const uint64_t *)key; + const uint64_t * end = data + (len/8); + + while(data != end) + { + uint64_t k = *data++; + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + + const unsigned char * data2 = (const unsigned char*)data; + + switch(len & 7) + { + case 7: h ^= uint64_t(data2[6]) << 48; + case 6: h ^= uint64_t(data2[5]) << 40; + case 5: h ^= uint64_t(data2[4]) << 32; + case 4: h ^= uint64_t(data2[3]) << 24; + case 3: h ^= uint64_t(data2[2]) << 16; + case 2: h ^= uint64_t(data2[1]) << 8; + case 1: h ^= uint64_t(data2[0]); + h *= m; + }; + + h ^= h >> r; + h *= m; + h ^= h >> r; + + return h; +} + + +// 64-bit hash for 32-bit platforms + +uint64_t MurmurHash64B ( const void * key, int len, uint64_t seed ) +{ + const uint32_t m = 0x5bd1e995; + const int r = 24; + + uint32_t h1 = uint32_t(seed) ^ len; + uint32_t h2 = uint32_t(seed >> 32); + + const uint32_t * data = (const uint32_t *)key; + + while(len >= 8) + { + uint32_t k1 = *data++; + k1 *= m; k1 ^= k1 >> r; k1 *= m; + h1 *= m; h1 ^= k1; + len -= 4; + + uint32_t k2 = *data++; + k2 *= m; k2 ^= k2 >> r; k2 *= m; + h2 *= m; h2 ^= k2; + len -= 4; + } + + if(len >= 4) + { + uint32_t k1 = *data++; + k1 *= m; k1 ^= k1 >> r; k1 *= m; + h1 *= m; h1 ^= k1; + len -= 4; + } + + switch(len) + { + case 3: h2 ^= ((unsigned char*)data)[2] << 16; + case 2: h2 ^= ((unsigned char*)data)[1] << 8; + case 1: h2 ^= ((unsigned char*)data)[0]; + h2 *= m; + }; + + h1 ^= h2 >> 18; h1 *= m; + h2 ^= h1 >> 22; h2 *= m; + h1 ^= h2 >> 17; h1 *= m; + h2 ^= h1 >> 19; h2 *= m; + + uint64_t h = h1; + + h = (h << 32) | h2; + + return h; +} + +//----------------------------------------------------------------------------- +// MurmurHash2A, by Austin Appleby + +// This is a variant of MurmurHash2 modified to use the Merkle-Damgard +// construction. Bulk speed should be identical to Murmur2, small-key speed +// will be 10%-20% slower due to the added overhead at the end of the hash. + +// This variant fixes a minor issue where null keys were more likely to +// collide with each other than expected, and also makes the function +// more amenable to incremental implementations. + +#define mmix(h,k) { k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; } + +uint32_t MurmurHash2A ( const void * key, int len, uint32_t seed ) +{ + const uint32_t m = 0x5bd1e995; + const int r = 24; + uint32_t l = len; + + const unsigned char * data = (const unsigned char *)key; + + uint32_t h = seed; + + while(len >= 4) + { + uint32_t k = *(uint32_t*)data; + + mmix(h,k); + + data += 4; + len -= 4; + } + + uint32_t t = 0; + + switch(len) + { + case 3: t ^= data[2] << 16; + case 2: t ^= data[1] << 8; + case 1: t ^= data[0]; + }; + + mmix(h,t); + mmix(h,l); + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} + +//----------------------------------------------------------------------------- +// CMurmurHash2A, by Austin Appleby + +// This is a sample implementation of MurmurHash2A designed to work +// incrementally. + +// Usage - + +// CMurmurHash2A hasher +// hasher.Begin(seed); +// hasher.Add(data1,size1); +// hasher.Add(data2,size2); +// ... +// hasher.Add(dataN,sizeN); +// uint32_t hash = hasher.End() + +class CMurmurHash2A +{ +public: + + void Begin ( uint32_t seed = 0 ) + { + m_hash = seed; + m_tail = 0; + m_count = 0; + m_size = 0; + } + + void Add ( const unsigned char * data, int len ) + { + m_size += len; + + MixTail(data,len); + + while(len >= 4) + { + uint32_t k = *(uint32_t*)data; + + mmix(m_hash,k); + + data += 4; + len -= 4; + } + + MixTail(data,len); + } + + uint32_t End ( void ) + { + mmix(m_hash,m_tail); + mmix(m_hash,m_size); + + m_hash ^= m_hash >> 13; + m_hash *= m; + m_hash ^= m_hash >> 15; + + return m_hash; + } + +private: + + static const uint32_t m = 0x5bd1e995; + static const int r = 24; + + void MixTail ( const unsigned char * & data, int & len ) + { + while( len && ((len<4) || m_count) ) + { + m_tail |= (*data++) << (m_count * 8); + + m_count++; + len--; + + if(m_count == 4) + { + mmix(m_hash,m_tail); + m_tail = 0; + m_count = 0; + } + } + } + + uint32_t m_hash; + uint32_t m_tail; + uint32_t m_count; + uint32_t m_size; +}; + +//----------------------------------------------------------------------------- +// MurmurHashNeutral2, by Austin Appleby + +// Same as MurmurHash2, but endian- and alignment-neutral. +// Half the speed though, alas. + +uint32_t MurmurHashNeutral2 ( const void * key, int len, uint32_t seed ) +{ + const uint32_t m = 0x5bd1e995; + const int r = 24; + + uint32_t h = seed ^ len; + + const unsigned char * data = (const unsigned char *)key; + + while(len >= 4) + { + uint32_t k; + + k = data[0]; + k |= data[1] << 8; + k |= data[2] << 16; + k |= data[3] << 24; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + switch(len) + { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} + +//----------------------------------------------------------------------------- +// MurmurHashAligned2, by Austin Appleby + +// Same algorithm as MurmurHash2, but only does aligned reads - should be safer +// on certain platforms. + +// Performance will be lower than MurmurHash2 + +#define MIX(h,k,m) { k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; } + + +uint32_t MurmurHashAligned2 ( const void * key, int len, uint32_t seed ) +{ + const uint32_t m = 0x5bd1e995; + const int r = 24; + + const unsigned char * data = (const unsigned char *)key; + + uint32_t h = seed ^ len; + + int align = (uint64_t)data & 3; + + if(align && (len >= 4)) + { + // Pre-load the temp registers + + uint32_t t = 0, d = 0; + + switch(align) + { + case 1: t |= data[2] << 16; + case 2: t |= data[1] << 8; + case 3: t |= data[0]; + } + + t <<= (8 * align); + + data += 4-align; + len -= 4-align; + + int sl = 8 * (4-align); + int sr = 8 * align; + + // Mix + + while(len >= 4) + { + d = *(uint32_t *)data; + t = (t >> sr) | (d << sl); + + uint32_t k = t; + + MIX(h,k,m); + + t = d; + + data += 4; + len -= 4; + } + + // Handle leftover data in temp registers + + d = 0; + + if(len >= align) + { + switch(align) + { + case 3: d |= data[2] << 16; + case 2: d |= data[1] << 8; + case 1: d |= data[0]; + } + + uint32_t k = (t >> sr) | (d << sl); + MIX(h,k,m); + + data += align; + len -= align; + + //---------- + // Handle tail bytes + + switch(len) + { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + } + else + { + switch(len) + { + case 3: d |= data[2] << 16; + case 2: d |= data[1] << 8; + case 1: d |= data[0]; + case 0: h ^= (t >> sr) | (d << sl); + h *= m; + } + } + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; + } + else + { + while(len >= 4) + { + uint32_t k = *(uint32_t *)data; + + MIX(h,k,m); + + data += 4; + len -= 4; + } + + //---------- + // Handle tail bytes + + switch(len) + { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; + } +} + +//----------------------------------------------------------------------------- diff --git a/extern/murmurhash/MurmurHash2.h b/extern/murmurhash/MurmurHash2.h new file mode 100644 index 000000000..e6d0c3692 --- /dev/null +++ b/extern/murmurhash/MurmurHash2.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// MurmurHash2 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +#ifndef _MURMURHASH2_H_ +#define _MURMURHASH2_H_ + +//----------------------------------------------------------------------------- +// Platform-specific functions and macros + +// Microsoft Visual Studio + +#if defined(_MSC_VER) && (_MSC_VER < 1600) + +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +typedef unsigned __int64 uint64_t; + +// Other compilers + +#else // defined(_MSC_VER) + +#include + +#endif // !defined(_MSC_VER) + +//----------------------------------------------------------------------------- + +uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ); +uint64_t MurmurHash64A ( const void * key, int len, uint64_t seed ); +uint64_t MurmurHash64B ( const void * key, int len, uint64_t seed ); +uint32_t MurmurHash2A ( const void * key, int len, uint32_t seed ); +uint32_t MurmurHashNeutral2 ( const void * key, int len, uint32_t seed ); +uint32_t MurmurHashAligned2 ( const void * key, int len, uint32_t seed ); + +//----------------------------------------------------------------------------- + +#endif // _MURMURHASH2_H_ From 003b0d48be3bff632e4c941c8a1dbf536293f4a4 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 19 Dec 2015 17:39:02 +1100 Subject: [PATCH 348/441] Move resource names listing code out of CSMWorld::Resources ctor in order to avoid multiple scan of the same resources. - The resources are still scanned twice, once when the archive/directory is added and another time when the names are listed. --- apps/opencs/model/world/resources.cpp | 20 ++------- apps/opencs/model/world/resources.hpp | 3 ++ apps/opencs/model/world/resourcesmanager.cpp | 45 +++++++++++++++++--- components/bsa/bsa_archive.cpp | 6 ++- 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index fc5b127dc..c5f5d6c76 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -4,30 +4,18 @@ #include #include -#include - #include CSMWorld::Resources::Resources (const std::string& baseDirectory, UniversalId::Type type, - const char * const *extensions) + std::vector resources, const char * const *extensions) : mBaseDirectory (baseDirectory), mType (type) { int baseSize = mBaseDirectory.size(); - Ogre::StringVector resourcesGroups = - Ogre::ResourceGroupManager::getSingleton().getResourceGroups(); - - for (Ogre::StringVector::iterator iter (resourcesGroups.begin()); - iter!=resourcesGroups.end(); ++iter) + for (std::vector::iterator iter(resources.begin()); iter != resources.end(); ++iter) { - if (*iter=="General" || *iter=="Internal" || *iter=="Autodetect") - continue; - - Ogre::StringVectorPtr resources = - Ogre::ResourceGroupManager::getSingleton().listResourceNames (*iter); - - for (Ogre::StringVector::const_iterator iter2 (resources->begin()); - iter2!=resources->end(); ++iter2) + // populate mFiles and mIndex + for (Ogre::StringVector::const_iterator iter2 ((*iter)->begin()); iter2 != (*iter)->end(); ++iter2) { if (static_cast (iter2->size())substr (0, baseSize)!=mBaseDirectory || diff --git a/apps/opencs/model/world/resources.hpp b/apps/opencs/model/world/resources.hpp index 9c1c76b6f..6e318e092 100644 --- a/apps/opencs/model/world/resources.hpp +++ b/apps/opencs/model/world/resources.hpp @@ -5,6 +5,8 @@ #include #include +#include + #include "universalid.hpp" namespace CSMWorld @@ -20,6 +22,7 @@ namespace CSMWorld /// \param type Type of resources in this table. Resources (const std::string& baseDirectory, UniversalId::Type type, + std::vector resources, const char * const *extensions = 0); int getSize() const; diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index 1b4770197..ba1d9629c 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -2,6 +2,8 @@ #include +#include + void CSMWorld::ResourcesManager::addResources (const Resources& resources) { mResources.insert (std::make_pair (resources.getType(), resources)); @@ -11,14 +13,45 @@ void CSMWorld::ResourcesManager::addResources (const Resources& resources) void CSMWorld::ResourcesManager::listResources() { + // Following code was taken out of Resources ctor, since it was being executed each time + // and slow enough to showe up in the profiler. + // + // See Editor ctor which calls Bsa::registerResources() + // + // resourceGroups include those from config files, e.g.: + // + // C:/Program Files\OpenMW\data + // C:/Program Files (x86)\Bethesda Softworks\Morrowind\Data Files + // + // and from archives: + // + // C:/Program Files (x86)\Bethesda Softworks\Morrowind\Data Files\Morrowind.bsa + // C:/Program Files (x86)\Bethesda Softworks\Morrowind\Data Files\Tribunal.bsa + // C:/Program Files (x86)\Bethesda Softworks\Morrowind\Data Files\Bloodmoon.bsa + // + std::vector resources; + + Ogre::StringVector resourcesGroups = + Ogre::ResourceGroupManager::getSingleton().getResourceGroups(); + + for (Ogre::StringVector::iterator iter (resourcesGroups.begin()); + iter!=resourcesGroups.end(); ++iter) + { + if (*iter=="General" || *iter=="Internal" || *iter=="Autodetect") + continue; + + resources.push_back( + Ogre::ResourceGroupManager::getSingleton().listResourceNames (*iter)); + } + static const char * const sMeshTypes[] = { "nif", 0 }; - addResources (Resources ("meshes", UniversalId::Type_Mesh, sMeshTypes)); - addResources (Resources ("icons", UniversalId::Type_Icon)); - addResources (Resources ("music", UniversalId::Type_Music)); - addResources (Resources ("sound", UniversalId::Type_SoundRes)); - addResources (Resources ("textures", UniversalId::Type_Texture)); - addResources (Resources ("videos", UniversalId::Type_Video)); + addResources (Resources ("meshes", UniversalId::Type_Mesh, resources, sMeshTypes)); + addResources (Resources ("icons", UniversalId::Type_Icon, resources)); + addResources (Resources ("music", UniversalId::Type_Music, resources)); + addResources (Resources ("sound", UniversalId::Type_SoundRes, resources)); + addResources (Resources ("textures", UniversalId::Type_Texture, resources)); + addResources (Resources ("videos", UniversalId::Type_Video, resources)); } const CSMWorld::Resources& CSMWorld::ResourcesManager::get (UniversalId::Type type) const diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 35fbf62a8..12836ea70 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -49,6 +49,8 @@ using namespace Ogre; static bool fsstrict = false; +static const std::ctype& facet = std::use_facet >(std::locale::classic()); + static char strict_normalize_char(char ch) { return ch == '\\' ? '/' : ch; @@ -56,7 +58,7 @@ static char strict_normalize_char(char ch) static char nonstrict_normalize_char(char ch) { - return ch == '\\' ? '/' : std::tolower(ch,std::locale::classic()); + return ch == '\\' ? '/' : facet.tolower(ch); } template @@ -245,7 +247,7 @@ public: time_t getModifiedTime(const String&) { return 0; } - // This is never called as far as I can see. + // This is never called as far as I can see. (actually called from CSMWorld::Resources ctor) StringVectorPtr list(bool recursive = true, bool dirs = false) { return find ("*", recursive, dirs); From abe6904a9494d9acf52d5bc512e921f77b7dccef Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 19 Dec 2015 17:54:45 +1100 Subject: [PATCH 349/441] Minor performance gains on loading cell references. - Use integers where possible - Unfortunately many functions are simply duplicated for now, but over time the deprecated ones will be removed --- components/esm/cellref.cpp | 12 +++--- components/esm/defs.hpp | 4 +- components/esm/esmreader.cpp | 81 ++++++++++++++++++++++++++++++++++++ components/esm/esmreader.hpp | 20 +++++++++ 4 files changed, 110 insertions(+), 7 deletions(-) diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index b3fb410e3..2aa36867b 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -6,9 +6,9 @@ void ESM::RefNum::load (ESMReader& esm, bool wide) { if (wide) - esm.getHNT (*this, "FRMR", 8); + esm.getHNT (*this, SREC_FRMR, 8); else - esm.getHNT (mIndex, "FRMR"); + esm.getHNT (mIndex, SREC_FRMR); } void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const @@ -36,14 +36,14 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) // the following refs are part of a "temp refs" section. A temp ref is not being tracked by the moved references system. // Its only purpose is a performance optimization for "immovable" things. We don't need this, and it's problematic anyway, // because any item can theoretically be moved by a script. - if (esm.isNextSub ("NAM0")) + if (esm.isNextSub (SREC_NAM0)) esm.skipHSub(); blank(); mRefNum.load (esm, wideRefNum); - mRefID = esm.getHNString ("NAME"); + esm.getHNString (SREC_NAME, mRefID); } void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) @@ -180,7 +180,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool void ESM::CellRef::blank() { mRefNum.unset(); - mRefID.clear(); + mRefID.clear(); mScale = 1; mOwner.clear(); mGlobalVariable.clear(); @@ -196,7 +196,7 @@ void ESM::CellRef::blank() mTrap.clear(); mReferenceBlocked = -1; mTeleport = false; - + for (int i=0; i<3; ++i) { mDoorDest.pos[i] = 0; diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 9dc088596..853c63c06 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -128,7 +128,9 @@ enum RecNameInts enum SubRecNameInts { SREC_DELE = ESM::FourCC<'D','E','L','E'>::value, - SREC_NAME = ESM::FourCC<'N','A','M','E'>::value + SREC_NAME = ESM::FourCC<'N','A','M','E'>::value, + SREC_NAM0 = ESM::FourCC<'N','A','M','0'>::value, + SREC_FRMR = ESM::FourCC<'F','R','M','R'>::value }; } diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 400ead4e2..b4b44f876 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -109,6 +109,12 @@ std::string ESMReader::getHNString(const char* name) return getHString(); } +void ESMReader::getHNString(const int name, std::string& str) +{ + getSubNameIs(name); + getHString(str); +} + std::string ESMReader::getHString() { getSubHeader(); @@ -130,6 +136,28 @@ std::string ESMReader::getHString() return getString(mCtx.leftSub); } +void ESMReader::getHString(std::string& str) +{ + getSubHeader(); + + // Hack to make MultiMark.esp load. Zero-length strings do not + // occur in any of the official mods, but MultiMark makes use of + // them. For some reason, they break the rules, and contain a byte + // (value 0) even if the header says there is no data. If + // Morrowind accepts it, so should we. + if (mCtx.leftSub == 0) + { + // Skip the following zero byte + mCtx.leftRec--; + char c; + getExact(&c, 1); + str = ""; + return; + } + + getString(str, mCtx.leftSub); +} + void ESMReader::getHExact(void*p, int size) { getSubHeader(); @@ -159,6 +187,24 @@ void ESMReader::getSubNameIs(const char* name) + mCtx.subName.toString()); } +void ESMReader::getSubNameIs(const int name) +{ + getSubName(); + if (mCtx.subName != name) + { + unsigned char typeName[4]; + typeName[0] = name & 0xff; + typeName[1] = (name >> 8) & 0xff; + typeName[2] = (name >> 16) & 0xff; + typeName[3] = (name >> 24) & 0xff; + + std::string subName = std::string((char*)typeName, 4); + + fail("Expected subrecord " + subName + " but got " + + mCtx.subName.toString()); + } +} + bool ESMReader::isNextSub(const char* name) { if (!mCtx.leftRec) @@ -174,6 +220,21 @@ bool ESMReader::isNextSub(const char* name) return !mCtx.subCached; } +bool ESMReader::isNextSub(const int name) +{ + if (!mCtx.leftRec) + return false; + + getSubName(); + + // If the name didn't match, then mark the it as 'cached' so it's + // available for the next call to getSubName. + mCtx.subCached = (mCtx.subName != name); + + // If subCached is false, then subName == name. + return !mCtx.subCached; +} + bool ESMReader::peekNextSub(const char *name) { if (!mCtx.leftRec) @@ -347,6 +408,26 @@ std::string ESMReader::getString(int size) return std::string (ptr, size); } +void ESMReader::getString(std::string& str, int size) +{ + size_t s = size; + if (mBuffer.size() <= s) + // Add some extra padding to reduce the chance of having to resize again later. + mBuffer.resize(3*s); + + mBuffer[s] = 0; // And make sure the string is zero terminated + + char *ptr = &mBuffer[0]; + getExact(ptr, size); // read ESM data + + size = static_cast(strnlen(ptr, size)); + + if (mEncoder) + str = mEncoder->getUtf8(ptr, size); // Convert to UTF8 and return + else + str = std::string (ptr, size); +} + void ESMReader::fail(const std::string &msg) { using namespace std; diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 66ef98130..3a2f32640 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -103,6 +103,13 @@ public: getHT(x); } + template + void getHNT(X &x, const int name) + { + getSubNameIs(name); + getHT(x); + } + // Optional version of getHNT template void getHNOT(X &x, const char* name) @@ -121,6 +128,14 @@ public: getHT(x); } + template + void getHNT(X &x, const int name, int size) + { + assert(sizeof(X) == size); + getSubNameIs(name); + getHT(x); + } + template void getHNOT(X &x, const char* name, int size) { @@ -159,9 +174,11 @@ public: // Read a string with the given sub-record name std::string getHNString(const char* name); + void getHNString(const int name, std::string& str); // Read a string, including the sub-record header (but not the name) std::string getHString(); + void getHString(std::string& str); // Read the given number of bytes from a subrecord void getHExact(void*p, int size); @@ -177,6 +194,7 @@ public: // Get the next subrecord name and check if it matches the parameter void getSubNameIs(const char* name); + void getSubNameIs(const int name); /** Checks if the next sub record name matches the parameter. If it does, it is read into 'subName' just as if getSubName() was @@ -184,6 +202,7 @@ public: calls to getSubName(), isNextSub() and getSubNameIs(). */ bool isNextSub(const char* name); + bool isNextSub(const int name); bool peekNextSub(const char* name); @@ -256,6 +275,7 @@ public: // Read the next 'size' bytes and return them as a string. Converts // them from native encoding to UTF8 in the process. std::string getString(int size); + void getString(std::string& str, int size); void skip(int bytes) { mEsm->seek(mEsm->tell()+bytes); } uint64_t getOffset() { return mEsm->tell(); } From 90b76801f6ebe95dba721c2b6ccc997ca43a7593 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 19 Dec 2015 18:50:07 +1100 Subject: [PATCH 350/441] Reduce the call to tolower() for each character when the string is already in lower case. - only a minor performance gain according to the MSVC profiler --- apps/opencs/model/world/infocollection.cpp | 3 ++- components/misc/stringops.hpp | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 7b7c274a0..f508b683e 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -106,10 +106,11 @@ int CSMWorld::InfoCollection::getInfoIndex (const std::string& id, const std::st return -1; // brute force loop + std::string lowerId = Misc::StringUtils::lowerCase (id); for (std::vector >::const_iterator it = iter->second.begin(); it != iter->second.end(); ++it) { - if (Misc::StringUtils::ciEqual(it->first, id)) + if (Misc::StringUtils::cEqual(it->first, lowerId)) return it->second; } diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index d6bc19069..1f6da37c7 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -35,6 +35,20 @@ public: return true; } + static bool cEqual(const std::string &x, const std::string &y) { + if (x.size() != y.size()) { + return false; + } + std::string::const_iterator xit = x.begin(); + std::string::const_iterator yit = y.begin(); + for (; xit != x.end(); ++xit, ++yit) { + if (*xit != *yit) { + return false; + } + } + return true; + } + static int ciCompareLen(const std::string &x, const std::string &y, size_t len) { std::string::const_iterator xit = x.begin(); From 60d5ea7ab6aeddeffb249c5c53e8a8dd4b36c1f3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 19 Dec 2015 20:57:29 +1100 Subject: [PATCH 351/441] Fix bug where new addons could not be created since commit ff072441fde05d189cb06cb44cc9c360690c2f09. --- apps/opencs/model/world/data.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 64d317bd4..7214e7e34 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include @@ -905,9 +907,12 @@ int CSMWorld::Data::getTotalRecords (const std::vector& for (unsigned int i = 0; i < files.size(); ++i) { - reader->open(files[i].string()); - records += reader->getRecordCount(); - reader->close(); + if (boost::filesystem::exists(files[i].string())) + { + reader->open(files[i].string()); + records += reader->getRecordCount(); + reader->close(); + } } return records; @@ -1113,7 +1118,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) else { mTopics.load (record, mBase); - mDialogue = &mTopics.getRecord (record.mId).get(); + mDialogue = &mTopics.getRecord (record.mId).get(); } } From 258d98cddc51556b262453bc80772a2115211fc2 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 19 Dec 2015 20:59:56 +1100 Subject: [PATCH 352/441] Update README.md regarding the cc9cii fork. --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index f62800e1f..95380e39b 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,11 @@ Morrowind by Bethesda Softworks. You need to own and install the original game f Font Licenses: * DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information) +Wrong Way, Go Back +------------------ + +This is a fork of an old version of OpenMW. This version is probably not what you are looking for. It is still stuck on Ogre 1.9 and hence does not have any of the recent graphics enhancements. I suggest you use the [official release](https://github.com/OpenMW/openmw) instead. + Getting Started --------------- @@ -97,3 +102,26 @@ Command line options --export-fonts [=arg(=1)] (=0) Export Morrowind .fnt fonts to PNG image and XML file in current directory --activate-dist arg (=-1) activation distance override + +Changes +------- + +Some of the differences with the official release are listed below. They are almost all to do with OpenCS. + +* Various minor bug fixes. +* Experimental support of loading TES4/TES5 records (coming soon). +* Experimental support of navMesh (coming soon). +* C++11 features are used (or at least those available on MSVC 2013 onwards). +* Loading time improvements. +* Loading progress bar changes. +* Pathgrid points supported. +* 3D editing retained, but does not have OSG enhancements or mouse modes. +* Modifying an object in the cell view should trigger the instances table to scroll to the corresponding record. +* Initial support for Land and Land Texture records. +* NPC stats autocalculation. +* Per-subview shortcuts for "added" and "modified" filters. +* Global filters + warning splash screen. +* User preferences setting to save the window state (position, geometry, etc) at the time of exit. +* User preferences setting to workaround some X window managers not keeping pre-maximised state. +* Use opencs.ini to store window states between sessions. + From c2ea682f0e04985d35a6ea4512798f08b5dd7674 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 20 Dec 2015 15:02:18 +1100 Subject: [PATCH 353/441] Set editor limits on hard-coded string lengths (Bug #3066) - See https://forum.openmw.org/viewtopic.php?f=2&t=3233 and https://bugs.openmw.org/issues/3066 --- apps/opencs/model/world/columnbase.cpp | 3 ++- apps/opencs/model/world/columnbase.hpp | 1 + apps/opencs/model/world/columnimp.hpp | 3 ++- apps/opencs/model/world/data.cpp | 8 ++++-- apps/opencs/model/world/refidcollection.cpp | 10 ++++--- apps/opencs/view/world/cellcreator.cpp | 9 ++++++- apps/opencs/view/world/genericcreator.cpp | 5 ++++ apps/opencs/view/world/genericcreator.hpp | 2 ++ .../view/world/idcompletiondelegate.cpp | 18 ++++++++++++- .../view/world/referenceablecreator.cpp | 26 +++++++++++++++++++ .../view/world/referenceablecreator.hpp | 3 +++ apps/opencs/view/world/util.cpp | 10 ++++++- 12 files changed, 88 insertions(+), 10 deletions(-) diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 1f16c9695..a4fe98de9 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -103,7 +103,8 @@ bool CSMWorld::ColumnBase::isId (Display display) bool CSMWorld::ColumnBase::isText (Display display) { return display==Display_String || display==Display_LongString || - display==Display_String32 || display==Display_LongString256; + display==Display_String32 || display==Display_String64 || + display==Display_LongString256; } bool CSMWorld::ColumnBase::isScript (Display display) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index c75a3c2a1..dec9ee083 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -129,6 +129,7 @@ namespace CSMWorld Display_InfoCondVar, Display_InfoCondComp, Display_String32, + Display_String64, Display_LongString256, Display_EffectSkill, // must display at least one, unlike Display_Skill diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 4e608dbbd..24414db64 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -317,7 +317,8 @@ namespace CSMWorld template struct NameColumn : public Column { - NameColumn() : Column (Columns::ColumnId_Name, ColumnBase::Display_String) {} + NameColumn(ColumnBase::Display display = ColumnBase::Display_String) + : Column (Columns::ColumnId_Name, display) {} virtual QVariant get (const Record& record) const { diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 7214e7e34..d723ddaac 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -107,7 +107,8 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mFactions.addColumn (new StringIdColumn); mFactions.addColumn (new RecordStateColumn); mFactions.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Faction)); - mFactions.addColumn (new NameColumn); + // The savegame format limits the player faction string to 32 characters. + mFactions.addColumn (new NameColumn(ColumnBase::Display_String32)); mFactions.addColumn (new AttributesColumn (0)); mFactions.addColumn (new AttributesColumn (1)); mFactions.addColumn (new HiddenColumn); @@ -117,6 +118,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mFactions.addColumn (new NestedParentColumn (Columns::ColumnId_FactionReactions)); index = mFactions.getColumns()-1; mFactions.addAdapter (std::make_pair(&mFactions.getColumn(index), new FactionReactionsAdapter ())); + // NAME32 enforced in IdCompletionDelegate::createEditor() mFactions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Faction, ColumnBase::Display_Faction)); mFactions.getNestableColumn(index)->addColumn( @@ -185,6 +187,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc index = mRegions.getColumns()-1; mRegions.addAdapter (std::make_pair(&mRegions.getColumn(index), new RegionSoundListAdapter ())); mRegions.getNestableColumn(index)->addColumn( + // NAME32 enforced in IdCompletionDelegate::createEditor() new NestedChildColumn (Columns::ColumnId_SoundName, ColumnBase::Display_Sound)); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_SoundChance, ColumnBase::Display_Integer)); @@ -291,7 +294,8 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mCells.addColumn (new StringIdColumn); mCells.addColumn (new RecordStateColumn); mCells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Cell)); - mCells.addColumn (new NameColumn); + // NAME64 enforced in IdCompletionDelegate::createEditor() + mCells.addColumn (new NameColumn(ColumnBase::Display_String64)); mCells.addColumn (new FlagColumn (Columns::ColumnId_SleepForbidden, ESM::Cell::NoSleep)); mCells.addColumn (new FlagColumn (Columns::ColumnId_InteriorWater, ESM::Cell::HasWater, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 7737e62c0..2cc6e43ea 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -58,8 +58,11 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) NameColumns nameColumns (modelColumns); - mColumns.push_back (RefIdColumn (Columns::ColumnId_Name, ColumnBase::Display_String)); + // Only items that can be placed in a container have the 32 character limit, but enforce + // that for all referenceable types for now. + mColumns.push_back (RefIdColumn (Columns::ColumnId_Name, ColumnBase::Display_String32)); nameColumns.mName = &mColumns.back(); + // NAME32 enforced in IdCompletionDelegate::createEditor() mColumns.push_back (RefIdColumn (Columns::ColumnId_Script, ColumnBase::Display_Script)); nameColumns.mScript = &mColumns.back(); @@ -239,9 +242,9 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String)); + new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String32)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiTargetId, CSMWorld::ColumnBase::Display_String)); + new RefIdColumn (Columns::ColumnId_AiTargetId, CSMWorld::ColumnBase::Display_String32)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiTargetCell, CSMWorld::ColumnBase::Display_String)); mColumns.back().addColumn( @@ -486,6 +489,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mColumns.push_back (RefIdColumn (Columns::ColumnId_Class, ColumnBase::Display_Class)); npcColumns.mClass = &mColumns.back(); + // NAME32 enforced in IdCompletionDelegate::createEditor() mColumns.push_back (RefIdColumn (Columns::ColumnId_Faction, ColumnBase::Display_Faction)); npcColumns.mFaction = &mColumns.back(); diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index 2a710a940..e8bc0d632 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -84,6 +84,13 @@ void CSVWorld::CellCreator::setType (int index) mYLabel->setVisible (index==1); mY->setVisible (index==1); + // The cell name is limited to 64 characters. (ESM::Header::GMDT::mCurrentCell) + std::string text = mType->currentText().toStdString(); + if (text == "Interior Cell") + GenericCreator::setEditorMaxLength (64); + else + GenericCreator::setEditorMaxLength (32767); + update(); } @@ -92,7 +99,7 @@ void CSVWorld::CellCreator::valueChanged (int index) update(); } -void CSVWorld::CellCreator::cloneMode(const std::string& originId, +void CSVWorld::CellCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { CSVWorld::GenericCreator::cloneMode(originId, type); diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index df7739941..85f6f8ac9 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -174,6 +174,11 @@ CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undo connect (&mData, SIGNAL (idListChanged()), this, SLOT (dataIdListChanged())); } +void CSVWorld::GenericCreator::setEditorMaxLength (int length) +{ + mId->setMaxLength (length); +} + void CSVWorld::GenericCreator::setEditLock (bool locked) { mLocked = locked; diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index f63c45109..41f88e868 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -78,6 +78,8 @@ namespace CSVWorld std::string getNamespace() const; + void setEditorMaxLength(int length); + private: void updateNamespace(); diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 970490828..e2a794f2c 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -30,11 +30,27 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); editor->setCompleter(completionManager.getCompleter(display).get()); + // The savegame format limits the player faction string to 32 characters. + // The region sound name is limited to 32 characters. (ESM::Region::SoundRef::mSound) + // The script name is limited to 32 characters. (ESM::Script::SCHD::mName) + // The cell name is limited to 64 characters. (ESM::Header::GMDT::mCurrentCell) + if (display == CSMWorld::ColumnBase::Display_Faction || + display == CSMWorld::ColumnBase::Display_Sound || + display == CSMWorld::ColumnBase::Display_Script || + display == CSMWorld::ColumnBase::Display_Referenceable) + { + editor->setMaxLength (32); + } + else if (display == CSMWorld::ColumnBase::Display_Cell) + { + editor->setMaxLength (64); + } + return editor; } CSVWorld::CommandDelegate *CSVWorld::IdCompletionDelegateFactory::makeDelegate(CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document& document, + CSMDoc::Document& document, QObject *parent) const { return new IdCompletionDelegate(dispatcher, document, parent); diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index 836e8ac7d..5de5f71d0 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -33,6 +33,8 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd } insertBeforeButtons (mType, false); + + connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int))); } void CSVWorld::ReferenceableCreator::reset() @@ -53,3 +55,27 @@ void CSVWorld::ReferenceableCreator::toggleWidgets(bool active) CSVWorld::GenericCreator::toggleWidgets(active); mType->setEnabled(active); } + +void CSVWorld::ReferenceableCreator::setType (int index) +{ + // container items have name limit of 32 characters + std::string text = mType->currentText().toStdString(); + if (text == "Potion" || + text == "Apparatus" || + text == "Armor" || + text == "Book" || + text == "Clothing" || + text == "Ingredient" || + text == "ItemLevelledList" || + text == "Light" || + text == "Lockpick" || + text == "Miscellaneous" || + text == "Probe" || + text == "Repair" || + text == "Weapon") + { + GenericCreator::setEditorMaxLength (32); + } + else + GenericCreator::setEditorMaxLength (32767); +} diff --git a/apps/opencs/view/world/referenceablecreator.hpp b/apps/opencs/view/world/referenceablecreator.hpp index 14ad24b29..57b5b0312 100644 --- a/apps/opencs/view/world/referenceablecreator.hpp +++ b/apps/opencs/view/world/referenceablecreator.hpp @@ -29,6 +29,9 @@ namespace CSVWorld virtual void toggleWidgets(bool active = true); + private slots: + + void setType (int index); }; } diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 1da9878ea..179f799c0 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -261,7 +261,15 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO widget->setMaxLength (32); return widget; } - + + case CSMWorld::ColumnBase::Display_String64: + { + // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used + CSVWidget::DropLineEdit *widget = new CSVWidget::DropLineEdit(display, parent); + widget->setMaxLength (64); + return widget; + } + default: return QStyledItemDelegate::createEditor (parent, option, index); From 4b0aeb406601cb30e63161b3bcceb6ecfbe8e85c Mon Sep 17 00:00:00 2001 From: dteviot Date: Sun, 15 Mar 2015 14:07:47 +1300 Subject: [PATCH 354/441] consolidate random number logic Note, I suspect Rng::rollClosedProbability() is not needed. The only difference between it and rollProbability() is that one time in 37k (on Windows), it will give an output of 1.0. On some versions of Linux, the value of 1.0 will occur about 1 time in 4 billion. (cherry picked from commit 3f28634d1f617691c672e41a3ee950e6daec8c77) # Conflicts: # apps/openmw/mwclass/creature.cpp # apps/openmw/mwclass/npc.cpp # apps/openmw/mwgui/pickpocketitemmodel.cpp # apps/openmw/mwgui/waitdialog.cpp # apps/openmw/mwmechanics/combat.cpp # apps/openmw/mwmechanics/mechanicsmanagerimp.cpp # components/CMakeLists.txt # libs/openengine/misc/rng.cpp --- apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwgui/pickpocketitemmodel.cpp | 2 +- apps/openmw/mwgui/waitdialog.cpp | 3 +-- apps/openmw/mwmechanics/combat.cpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- components/CMakeLists.txt | 7 +------ libs/openengine/misc/rng.cpp | 2 +- 8 files changed, 8 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 13fd22a0c..4043e9883 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -250,7 +250,7 @@ namespace MWClass float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat); - if(OEngine::Misc::Rng::roll0to99() >= hitchance) + if(OEngine::Misc::Rng::rollProbability() >= hitchance/100.0f) { victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index bc806078a..c71c54f99 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -382,7 +382,7 @@ namespace MWClass float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill)); - if (OEngine::Misc::Rng::roll0to99() >= hitchance) + if (OEngine::Misc::Rng::rollProbability() >= hitchance / 100.0f) { othercls.onHit(victim, 0.0f, false, weapon, ptr, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index b8ac20f99..bc7c5528e 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -20,7 +20,7 @@ namespace MWGui { for (size_t i = 0; igetItemCount(); ++i) { - if (OEngine::Misc::Rng::roll0to99() > chance) + if (chance <= OEngine::Misc::Rng::roll0to99()) mHiddenItems.push_back(mSourceModel->getItem(i)); } } diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index ed261e7eb..163222c16 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -155,9 +155,8 @@ namespace MWGui if (!region->mSleepList.empty()) { // figure out if player will be woken while sleeping - int x = OEngine::Misc::Rng::rollDice(hoursToWait); float fSleepRandMod = world->getStore().get().find("fSleepRandMod")->getFloat(); - if (x < fSleepRandMod * hoursToWait) + if (OEngine::Misc::Rng::rollProbability() > fSleepRandMod) { float fSleepRestMod = world->getStore().get().find("fSleepRestMod")->getFloat(); mInterruptAt = hoursToWait - int(fSleepRestMod * hoursToWait); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 045f0108c..c5fc34507 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -187,7 +187,7 @@ namespace MWMechanics int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); - if (OEngine::Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) + if (OEngine::Misc::Rng::rollProbability() >= getHitChance(attacker, victim, skillValue) / 100.0f) { victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index dc388555e..ee158a15d 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -741,7 +741,7 @@ namespace MWMechanics float x = 0; float y = 0; - int roll = OEngine::Misc::Rng::roll0to99(); + float roll = OEngine::Misc::Rng::rollClosedProbability() * 100; if (type == PT_Admire) { diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b2dbd3ff6..a5934b5f1 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -166,14 +166,9 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) target_link_libraries(components - ${Boost_SYSTEM_LIBRARY} - ${Boost_FILESYSTEM_LIBRARY} - ${Boost_THREAD_LIBRARY} - ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_WAVE_LIBRARY} + ${Boost_LIBRARIES} ${OGRE_LIBRARIES} ${OENGINE_LIBRARY} - ${BULLET_LIBRARIES} ) if (WIN32) diff --git a/libs/openengine/misc/rng.cpp b/libs/openengine/misc/rng.cpp index 3d50400df..140aa337e 100644 --- a/libs/openengine/misc/rng.cpp +++ b/libs/openengine/misc/rng.cpp @@ -26,4 +26,4 @@ namespace Misc { } } -} +} \ No newline at end of file From b4cdb965dc0c7a293b3df9ccc9bbe25af28c4f90 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 22 Apr 2015 17:58:55 +0200 Subject: [PATCH 355/441] Move rng to components (cherry picked from commit 8c810e36203b93780cae198a3f7e2fb33ee54b5d) # Conflicts: # apps/openmw/engine.cpp # apps/openmw/mwclass/npc.cpp # components/misc/rng.cpp # components/sceneutil/lightcontroller.cpp # libs/openengine/CMakeLists.txt --- apps/openmw/engine.cpp | 4 +- apps/openmw/mwclass/creature.cpp | 8 +- apps/openmw/mwclass/npc.cpp | 12 +- apps/openmw/mwgui/jailscreen.cpp | 4 +- apps/openmw/mwgui/loadingscreen.cpp | 4 +- apps/openmw/mwgui/pickpocketitemmodel.cpp | 4 +- apps/openmw/mwgui/recharge.cpp | 4 +- apps/openmw/mwgui/tradewindow.cpp | 4 +- apps/openmw/mwgui/waitdialog.cpp | 4 +- apps/openmw/mwmechanics/activespells.cpp | 4 +- apps/openmw/mwmechanics/aicombat.cpp | 24 ++-- apps/openmw/mwmechanics/aiwander.cpp | 10 +- apps/openmw/mwmechanics/alchemy.cpp | 6 +- apps/openmw/mwmechanics/combat.cpp | 10 +- apps/openmw/mwmechanics/disease.hpp | 4 +- apps/openmw/mwmechanics/enchanting.cpp | 4 +- apps/openmw/mwmechanics/levelledlist.hpp | 6 +- .../mwmechanics/mechanicsmanagerimp.cpp | 6 +- apps/openmw/mwmechanics/pickpocket.cpp | 4 +- apps/openmw/mwmechanics/repair.cpp | 4 +- apps/openmw/mwmechanics/security.cpp | 6 +- apps/openmw/mwmechanics/spellcasting.cpp | 16 +-- apps/openmw/mwmechanics/spells.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 +- apps/openmw/mwrender/sky.cpp | 2 +- apps/openmw/mwsound/soundmanagerimp.cpp | 8 +- apps/openmw/mwworld/inventorystore.cpp | 2 +- apps/openmw/mwworld/store.hpp | 4 +- apps/openmw/mwworld/weather.cpp | 8 +- apps/openmw/mwworld/worldimp.cpp | 6 +- components/CMakeLists.txt | 2 +- components/interpreter/miscopcodes.hpp | 4 +- {libs/openengine => components}/misc/rng.cpp | 5 +- {libs/openengine => components}/misc/rng.hpp | 6 +- components/sceneutil/lightcontroller.cpp | 129 ++++++++++++++++++ libs/openengine/CMakeLists.txt | 7 +- 36 files changed, 230 insertions(+), 111 deletions(-) rename {libs/openengine => components}/misc/rng.cpp (93%) rename {libs/openengine => components}/misc/rng.hpp (88%) create mode 100644 components/sceneutil/lightcontroller.cpp diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 9d40ebb1b..539539db9 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -11,7 +11,7 @@ #include -#include +#include #include @@ -194,7 +194,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mNewGame (false) , mCfgMgr(configurationManager) { - OEngine::Misc::Rng::init(); + Misc::Rng::init(); std::srand ( static_cast(std::time(NULL)) ); MWClass::registerClasses(); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4043e9883..87c4094ca 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -1,6 +1,6 @@ #include "creature.hpp" -#include +#include #include #include @@ -250,7 +250,7 @@ namespace MWClass float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat); - if(OEngine::Misc::Rng::rollProbability() >= hitchance/100.0f) + if(Misc::Rng::rollProbability() >= hitchance/100.0f) { victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); @@ -376,7 +376,7 @@ namespace MWClass float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().iKnockDownOddsMult->getInt() * 0.01f + getGmst().iKnockDownOddsBase->getInt(); - if (ishealth && agilityTerm <= damage && knockdownTerm <= OEngine::Misc::Rng::roll0to99()) + if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) { getCreatureStats(ptr).setKnockedDown(true); @@ -680,7 +680,7 @@ namespace MWClass ++sound; } if(!sounds.empty()) - return sounds[OEngine::Misc::Rng::rollDice(sounds.size())]->mSound; + return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; } if (type == ESM::SoundGenerator::Land) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c71c54f99..221dd3d8b 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -2,9 +2,7 @@ #include -#include - -#include +#include #include #include @@ -382,7 +380,7 @@ namespace MWClass float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill)); - if (OEngine::Misc::Rng::rollProbability() >= hitchance / 100.0f) + if (Misc::Rng::rollProbability() >= hitchance / 100.0f) { othercls.onHit(victim, 0.0f, false, weapon, ptr, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); @@ -512,7 +510,7 @@ namespace MWClass const GMST& gmst = getGmst(); int chance = store.get().find("iVoiceHitOdds")->getInt(); - if (OEngine::Misc::Rng::roll0to99() < chance) + if (Misc::Rng::roll0to99() < chance) { MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); } @@ -521,7 +519,7 @@ namespace MWClass float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat(); float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt(); - if (ishealth && agilityTerm <= damage && knockdownTerm <= OEngine::Misc::Rng::roll0to99()) + if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) { getCreatureStats(ptr).setKnockedDown(true); @@ -547,7 +545,7 @@ namespace MWClass MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet }; - int hitslot = hitslots[OEngine::Misc::Rng::rollDice(20)]; + int hitslot = hitslots[Misc::Rng::rollDice(20)]; float unmitigatedDamage = damage; float x = damage / (damage + getArmorRating(ptr)); diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 936da2d8e..e4a3a3147 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -85,7 +85,7 @@ namespace MWGui std::set skills; for (int day=0; day #include -#include +#include #include @@ -148,7 +148,7 @@ namespace MWGui if (!mResources.empty()) { - std::string const & randomSplash = mResources.at(OEngine::Misc::Rng::rollDice(mResources.size())); + std::string const & randomSplash = mResources.at(Misc::Rng::rollDice(mResources.size())); Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index bc7c5528e..2620e5660 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -1,6 +1,6 @@ #include "pickpocketitemmodel.hpp" -#include +#include #include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" @@ -20,7 +20,7 @@ namespace MWGui { for (size_t i = 0; igetItemCount(); ++i) { - if (chance <= OEngine::Misc::Rng::roll0to99()) + if (chance <= Misc::Rng::roll0to99()) mHiddenItems.push_back(mSourceModel->getItem(i)); } } diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index b7280565b..76961af5d 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include @@ -165,7 +165,7 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) intelligenceTerm = 1; float x = (npcStats.getSkill(ESM::Skill::Enchant).getModified() + intelligenceTerm + luckTerm) * stats.getFatigueTerm(); - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (roll < x) { std::string soul = gem.getCellRef().getSoul(); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index bdcf28bf2..d2ffe60b1 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include @@ -368,7 +368,7 @@ namespace MWGui else x += abs(int(npcTerm - pcTerm)); - int roll = OEngine::Misc::Rng::rollDice(100) + 1; + int roll = Misc::Rng::rollDice(100) + 1; if(roll > x || (mCurrentMerchantOffer < 0) != (mCurrentBalance < 0)) //trade refused { MWBase::Environment::get().getWindowManager()-> diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 163222c16..cc4de51b6 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include @@ -156,7 +156,7 @@ namespace MWGui { // figure out if player will be woken while sleeping float fSleepRandMod = world->getStore().get().find("fSleepRandMod")->getFloat(); - if (OEngine::Misc::Rng::rollProbability() > fSleepRandMod) + if (Misc::Rng::rollProbability() > fSleepRandMod) { float fSleepRestMod = world->getStore().get().find("fSleepRestMod")->getFloat(); mInterruptAt = hoursToWait - int(fSleepRestMod * hoursToWait); diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index a6cc9af8e..7068310a0 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -1,6 +1,6 @@ #include "activespells.hpp" -#include +#include #include @@ -231,7 +231,7 @@ namespace MWMechanics { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ) { - if (OEngine::Misc::Rng::roll0to99() < chance) + if (Misc::Rng::roll0to99() < chance) mSpells.erase(it++); else ++it; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 5f0eff4e9..94d648ea3 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include @@ -395,7 +395,7 @@ namespace MWMechanics if (!distantCombat) attackType = chooseBestAttack(weapon, movement); else attackType = ESM::Weapon::AT_Chop; // cause it's =0 - strength = OEngine::Misc::Rng::rollClosedProbability(); + strength = Misc::Rng::rollClosedProbability(); // Note: may be 0 for some animations timerAttack = minMaxAttackDuration[attackType][0] + @@ -406,7 +406,7 @@ namespace MWMechanics { const MWWorld::ESMStore &store = world->getStore(); int chance = store.get().find("iVoiceAttackOdds")->getInt(); - if (OEngine::Misc::Rng::roll0to99() < chance) + if (Misc::Rng::roll0to99() < chance) { MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } @@ -513,17 +513,17 @@ namespace MWMechanics { if(movement.mPosition[0] || movement.mPosition[1]) { - timerCombatMove = 0.1f + 0.1f * OEngine::Misc::Rng::rollClosedProbability(); + timerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); combatMove = true; } // only NPCs are smart enough to use dodge movements else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2))) { //apply sideway movement (kind of dodging) with some probability - if (OEngine::Misc::Rng::rollClosedProbability() < 0.25) + if (Misc::Rng::rollClosedProbability() < 0.25) { - movement.mPosition[0] = OEngine::Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; - timerCombatMove = 0.05f + 0.15f * OEngine::Misc::Rng::rollClosedProbability(); + movement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; + timerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); combatMove = true; } } @@ -638,7 +638,7 @@ namespace MWMechanics float s2 = speed2 * t; float t_swing = minMaxAttackDuration[ESM::Weapon::AT_Thrust][0] + - (minMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * OEngine::Misc::Rng::rollClosedProbability(); + (minMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * Misc::Rng::rollClosedProbability(); if (t + s2/speed1 <= t_swing) { @@ -750,10 +750,10 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: if (weapon == NULL) { //hand-to-hand deal equal damage for each type - float roll = OEngine::Misc::Rng::rollClosedProbability(); + float roll = Misc::Rng::rollClosedProbability(); if(roll <= 0.333f) //side punch { - movement.mPosition[0] = OEngine::Misc::Rng::rollClosedProbability() ? 1.0f : -1.0f; + movement.mPosition[0] = Misc::Rng::rollClosedProbability() ? 1.0f : -1.0f; movement.mPosition[1] = 0; attackType = ESM::Weapon::AT_Slash; } @@ -777,10 +777,10 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: float total = static_cast(slash + chop + thrust); - float roll = OEngine::Misc::Rng::rollClosedProbability(); + float roll = Misc::Rng::rollClosedProbability(); if(roll <= (slash/total)) { - movement.mPosition[0] = (OEngine::Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; + movement.mPosition[0] = (Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; movement.mPosition[1] = 0; attackType = ESM::Weapon::AT_Slash; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index d89a29e1d..dffffb1d8 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include @@ -329,7 +329,7 @@ namespace MWMechanics static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() .get().find("fVoiceIdleOdds")->getFloat(); - float roll = OEngine::Misc::Rng::rollProbability() * 10000.0f; + float roll = Misc::Rng::rollProbability() * 10000.0f; // In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds // due to the roll being an integer. @@ -504,7 +504,7 @@ namespace MWMechanics if(!storage.mPathFinder.isPathConstructed()) { assert(mAllowedNodes.size()); - unsigned int randNode = OEngine::Misc::Rng::rollDice(mAllowedNodes.size()); + unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size()); // NOTE: initially constructed with local (i.e. cell) co-ordinates Ogre::Vector3 destNodePos(PathFinder::MakeOgreVector3(mAllowedNodes[randNode])); @@ -631,7 +631,7 @@ namespace MWMechanics .get().find("fIdleChanceMultiplier")->getFloat(); unsigned short idleChance = static_cast(fIdleChanceMultiplier * mIdle[counter]); - unsigned short randSelect = (int)(OEngine::Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); + unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); if(randSelect < idleChance && randSelect > idleRoll) { playedIdle = counter+2; @@ -653,7 +653,7 @@ namespace MWMechanics state.moveIn(new AiWanderStorage()); - int index = OEngine::Misc::Rng::rollDice(mAllowedNodes.size()); + int index = Misc::Rng::rollDice(mAllowedNodes.size()); ESM::Pathgrid::Point dest = mAllowedNodes[index]; // apply a slight offset to prevent overcrowding diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 42dd3048f..2c5e58589 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include @@ -298,7 +298,7 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) newRecord.mName = name; - int index = OEngine::Misc::Rng::rollDice(6); + int index = Misc::Rng::rollDice(6); assert (index>=0 && index<6); static const char *meshes[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; @@ -474,7 +474,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na return Result_RandomFailure; } - if (getAlchemyFactor() < OEngine::Misc::Rng::roll0to99()) + if (getAlchemyFactor() < Misc::Rng::roll0to99()) { removeIngredients(); return Result_RandomFailure; diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index c5fc34507..42aed3da3 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -109,7 +109,7 @@ namespace MWMechanics int iBlockMinChance = gmst.find("iBlockMinChance")->getInt(); x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x)); - if (OEngine::Misc::Rng::roll0to99() < x) + if (Misc::Rng::roll0to99() < x) { // Reduce shield durability by incoming damage int shieldhealth = shield->getClass().getItemHealth(*shield); @@ -187,7 +187,7 @@ namespace MWMechanics int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); - if (OEngine::Misc::Rng::rollProbability() >= getHitChance(attacker, victim, skillValue) / 100.0f) + if (Misc::Rng::rollProbability() >= getHitChance(attacker, victim, skillValue) / 100.0f) { victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); @@ -225,7 +225,7 @@ namespace MWMechanics && !appliedEnchantment) { float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); - if (OEngine::Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) + if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) victim.getClass().getContainerStore(victim).add(projectile, 1, victim); } @@ -292,7 +292,7 @@ namespace MWMechanics saveTerm *= 1.25f * normalisedFatigue; - float x = std::max(0.f, saveTerm - OEngine::Misc::Rng::roll0to99()); + float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99()); int element = ESM::MagicEffect::FireDamage; if (i == 1) diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 0153be3dc..cf21f3806 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MECHANICS_DISEASE_H #define OPENMW_MECHANICS_DISEASE_H -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" @@ -51,7 +51,7 @@ namespace MWMechanics continue; int x = static_cast(fDiseaseXferChance * 100 * resist); - if (OEngine::Misc::Rng::rollDice(10000) < x) + if (Misc::Rng::rollDice(10000) < x) { // Contracted disease! actor.getClass().getCreatureStats(actor).getSpells().add(it->first); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index bb02fb41d..2cb963e28 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -1,6 +1,6 @@ #include "enchanting.hpp" -#include +#include #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" @@ -70,7 +70,7 @@ namespace MWMechanics if(mSelfEnchanting) { - if(getEnchantChance() <= (OEngine::Misc::Rng::roll0to99())) + if(getEnchantChance() <= (Misc::Rng::roll0to99())) return false; mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index 20b87a3a9..14b68b9cd 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MECHANICS_LEVELLEDLIST_H #define OPENMW_MECHANICS_LEVELLEDLIST_H -#include +#include #include "../mwworld/ptr.hpp" #include "../mwworld/esmstore.hpp" @@ -24,7 +24,7 @@ namespace MWMechanics failChance += levItem->mChanceNone; - if (OEngine::Misc::Rng::roll0to99() < failChance) + if (Misc::Rng::roll0to99() < failChance) return std::string(); std::vector candidates; @@ -53,7 +53,7 @@ namespace MWMechanics } if (candidates.empty()) return std::string(); - std::string item = candidates[OEngine::Misc::Rng::rollDice(candidates.size())]; + std::string item = candidates[Misc::Rng::rollDice(candidates.size())]; // Vanilla doesn't fail on nonexistent items in levelled lists if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item))) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index ee158a15d..2bf23b586 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -2,7 +2,7 @@ #include "mechanicsmanagerimp.hpp" #include "npcstats.hpp" -#include +#include #include @@ -741,7 +741,7 @@ namespace MWMechanics float x = 0; float y = 0; - float roll = OEngine::Misc::Rng::rollClosedProbability() * 100; + float roll = Misc::Rng::rollClosedProbability() * 100; if (type == PT_Admire) { @@ -1399,7 +1399,7 @@ namespace MWMechanics float target = x - y; - return (OEngine::Misc::Rng::roll0to99() >= target); + return (Misc::Rng::roll0to99() >= target); } void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) diff --git a/apps/openmw/mwmechanics/pickpocket.cpp b/apps/openmw/mwmechanics/pickpocket.cpp index 561011df3..eca24606e 100644 --- a/apps/openmw/mwmechanics/pickpocket.cpp +++ b/apps/openmw/mwmechanics/pickpocket.cpp @@ -1,6 +1,6 @@ #include "pickpocket.hpp" -#include +#include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -41,7 +41,7 @@ namespace MWMechanics int iPickMaxChance = MWBase::Environment::get().getWorld()->getStore().get() .find("iPickMaxChance")->getInt(); - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (t < pcSneak / iPickMinChance) { return (roll > int(pcSneak / iPickMinChance)); diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index b5058fb88..fa429bbee 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -48,7 +48,7 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) float x = (0.1f * pcStrength + 0.1f * pcLuck + armorerSkill) * fatigueTerm; - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (roll <= x) { int y = static_cast(fRepairAmountMult * toolQuality * roll); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index 3f72f1b66..9eab5bfef 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -1,6 +1,6 @@ #include "security.hpp" -#include +#include #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -50,7 +50,7 @@ namespace MWMechanics else { MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, lock); - if (OEngine::Misc::Rng::roll0to99() <= x) + if (Misc::Rng::roll0to99() <= x) { lock.getClass().unlock(lock); resultMessage = "#{sLockSuccess}"; @@ -91,7 +91,7 @@ namespace MWMechanics else { MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, trap); - if (OEngine::Misc::Rng::roll0to99() <= x) + if (Misc::Rng::roll0to99() <= x) { trap.getCellRef().setTrap(""); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 1f3a88827..ce0f7c12e 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -282,7 +282,7 @@ namespace MWMechanics if (castChance > 0) x *= 50 / castChance; - float roll = OEngine::Misc::Rng::rollClosedProbability() * 100; + float roll = Misc::Rng::rollClosedProbability() * 100; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) roll -= resistance; @@ -385,7 +385,7 @@ namespace MWMechanics target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).getMagnitude() : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).getMagnitude(); - if (OEngine::Misc::Rng::roll0to99() <= x) + if (Misc::Rng::roll0to99() <= x) { // Fully resisted, show message if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) @@ -415,7 +415,7 @@ namespace MWMechanics if (spell && caster != target && target.getClass().isActor()) { float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude(); - absorbed = (OEngine::Misc::Rng::roll0to99() < absorb); + absorbed = (Misc::Rng::roll0to99() < absorb); if (absorbed) { const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); @@ -463,7 +463,7 @@ namespace MWMechanics if (!reflected && magnitudeMult > 0 && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); - bool isReflected = (OEngine::Misc::Rng::roll0to99() < reflect); + bool isReflected = (Misc::Rng::roll0to99() < reflect); if (isReflected) { const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); @@ -491,7 +491,7 @@ namespace MWMechanics if (magnitudeMult > 0 && !absorbed) { - float random = OEngine::Misc::Rng::rollClosedProbability(); + float random = Misc::Rng::rollClosedProbability(); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; magnitude *= magnitudeMult; @@ -823,7 +823,7 @@ namespace MWMechanics // Check success float successChance = getSpellSuccessChance(spell, mCaster); - if (OEngine::Misc::Rng::roll0to99() >= successChance) + if (Misc::Rng::roll0to99() >= successChance) { if (mCaster == MWBase::Environment::get().getWorld()->getPlayerPtr()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); @@ -901,7 +901,7 @@ namespace MWMechanics + 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()) * creatureStats.getFatigueTerm(); - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (roll > x) { // "X has no effect on you" diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 4636ecfae..ef75d5671 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -37,7 +37,7 @@ namespace MWMechanics for (unsigned int i=0; imEffects.mList.size();++i) { if (spell->mEffects.mList[i].mMagnMin != spell->mEffects.mList[i].mMagnMax) - random[i] = OEngine::Misc::Rng::rollClosedProbability(); + random[i] = Misc::Rng::rollClosedProbability(); } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index f4943ba55..2d0e05411 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -12,7 +12,7 @@ #include -#include +#include #include @@ -103,7 +103,7 @@ void HeadAnimationTime::setEnabled(bool enabled) void HeadAnimationTime::resetBlinkTimer() { - mBlinkTimer = -(2.0f + OEngine::Misc::Rng::rollDice(6)); + mBlinkTimer = -(2.0f + Misc::Rng::rollDice(6)); } void HeadAnimationTime::update(float dt) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index fd439050c..2aab1c0cc 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -19,7 +19,7 @@ #include -#include +#include #include #include diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index c7fb9ea50..731742361 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -226,7 +226,7 @@ namespace MWSound if(!filelist.size()) return; - int i = OEngine::Misc::Rng::rollDice(filelist.size()); + int i = Misc::Rng::rollDice(filelist.size()); // Don't play the same music track twice in a row if (filelist[i] == mLastPlayedMusic) @@ -561,7 +561,7 @@ namespace MWSound if(!cell->isExterior() || sTimePassed < sTimeToNextEnvSound) return; - float a = OEngine::Misc::Rng::rollClosedProbability(); + float a = Misc::Rng::rollClosedProbability(); // NOTE: We should use the "Minimum Time Between Environmental Sounds" and // "Maximum Time Between Environmental Sounds" fallback settings here. sTimeToNextEnvSound = 5.0f*a + 15.0f*(1.0f-a); @@ -590,7 +590,7 @@ namespace MWSound return; } - int r = OEngine::Misc::Rng::rollDice(total); + int r = Misc::Rng::rollDice(total); int pos = 0; soundIter = regn->mSoundList.begin(); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 5b0c2311a..e85dbbe58 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -366,7 +366,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) // Roll some dice, one for each effect params.resize(enchantment.mEffects.mList.size()); for (unsigned int i=0; i #include -#include +#include #include #include @@ -186,7 +186,7 @@ namespace MWWorld std::vector results; std::for_each(mShared.begin(), mShared.end(), GetRecords(id, &results)); if(!results.empty()) - return results[OEngine::Misc::Rng::rollDice(results.size())]; + return results[Misc::Rng::rollDice(results.size())]; return NULL; } diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 41ed7c20d..6e2620188 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -3,7 +3,7 @@ #include "weather.hpp" -#include +#include #include @@ -527,7 +527,7 @@ void WeatherManager::update(float duration, bool paused) if (mThunderSoundDelay <= 0) { // pick a random sound - int sound = OEngine::Misc::Rng::rollDice(4); + int sound = Misc::Rng::rollDice(4); std::string* soundName = NULL; if (sound == 0) soundName = &mThunderSoundID0; else if (sound == 1) soundName = &mThunderSoundID1; @@ -543,7 +543,7 @@ void WeatherManager::update(float duration, bool paused) mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold ); else { - mThunderChanceNeeded = static_cast(OEngine::Misc::Rng::rollDice(100)); + mThunderChanceNeeded = static_cast(Misc::Rng::rollDice(100)); mThunderChance = 0; mRendering->getSkyManager()->setLightningStrength( 0.f ); } @@ -625,7 +625,7 @@ std::string WeatherManager::nextWeather(const ESM::Region* region) const * 70% will be greater than 30 (in theory). */ - int chance = OEngine::Misc::Rng::rollDice(100) + 1; // 1..100 + int chance = Misc::Rng::rollDice(100) + 1; // 1..100 int sum = 0; unsigned int i = 0; for (; i < probability.size(); ++i) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b6d5c8e93..71cf4a143 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include @@ -3145,7 +3145,7 @@ namespace MWWorld const ESM::CreatureLevList* list = getStore().get().find(creatureList); int iNumberCreatures = getStore().get().find("iNumberCreatures")->getInt(); - int numCreatures = 1 + OEngine::Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures] + int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures] for (int i=0; igetFallbackString(modelName.str()); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index a5934b5f1..da0428ec2 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -70,7 +70,7 @@ add_component_dir (esmterrain ) add_component_dir (misc - utf8stream stringops resourcehelpers + utf8stream stringops resourcehelpers rng ) IF(NOT WIN32 AND NOT APPLE) diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index f566a5499..c49bbeb01 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -12,7 +12,7 @@ #include "runtime.hpp" #include "defines.hpp" -#include +#include namespace Interpreter { @@ -148,7 +148,7 @@ namespace Interpreter throw std::runtime_error ( "random: argument out of range (Don't be so negative!)"); - Type_Integer value = OEngine::Misc::Rng::rollDice(limit); // [o, limit) + Type_Integer value = Misc::Rng::rollDice(limit); // [o, limit) runtime[0].mInteger = value; } diff --git a/libs/openengine/misc/rng.cpp b/components/misc/rng.cpp similarity index 93% rename from libs/openengine/misc/rng.cpp rename to components/misc/rng.cpp index 140aa337e..df0fc687e 100644 --- a/libs/openengine/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -2,8 +2,8 @@ #include #include -namespace OEngine { -namespace Misc { +namespace Misc +{ void Rng::init() { @@ -26,4 +26,3 @@ namespace Misc { } } -} \ No newline at end of file diff --git a/libs/openengine/misc/rng.hpp b/components/misc/rng.hpp similarity index 88% rename from libs/openengine/misc/rng.hpp rename to components/misc/rng.hpp index 4e1da17e1..01fcdd763 100644 --- a/libs/openengine/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -1,9 +1,8 @@ -#ifndef MISC_RNG_H -#define MISC_RNG_H +#ifndef OPENMW_COMPONENTS_MISC_RNG_H +#define OPENMW_COMPONENTS_MISC_RNG_H #include -namespace OEngine { namespace Misc { @@ -30,7 +29,6 @@ public: static int roll0to99() { return rollDice(100); } }; -} } #endif diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp new file mode 100644 index 000000000..484cea447 --- /dev/null +++ b/components/sceneutil/lightcontroller.cpp @@ -0,0 +1,129 @@ +#include "lightcontroller.hpp" + +#include + +#include + +#include + +#include + +namespace +{ + + float pulseAmplitude(float time) + { + return std::sin(time); + } + + float flickerAmplitude(float time) + { + static const float fb = 1.17024f; + static const float f[3] = { 1.5708f, 4.18774f, 5.19934f }; + static const float o[3] = { 0.804248f, 2.11115f, 3.46832f }; + static const float m[3] = { 1.0f, 0.785f, 0.876f }; + static const float s = 0.394f; + + float v = 0.0f; + for(int i = 0;i < 3;++i) + v += std::sin(fb*time*f[i] + o[1])*m[i]; + return v * s; + } + + float flickerFrequency(float phase) + { + static const float fa = 0.785398f; + static const float tdo = 0.94f; + static const float tdm = 2.48f; + + return tdo + tdm*std::sin(fa * phase); + } + +} + +namespace SceneUtil +{ + + LightController::LightController() + : mType(LT_Normal) + , mPhase((Misc::Rng::rollClosedProbability() * 2.f - 1.f) * 500.f) + , mLastTime(0.0) + , mDeltaCount(0.f) + , mDirection(1.f) + { + } + + void LightController::setType(LightController::LightType type) + { + mType = type; + } + + void LightController::operator ()(osg::Node* node, osg::NodeVisitor* nv) + { + double time = nv->getFrameStamp()->getSimulationTime(); + if (time == mLastTime) + return; + + float dt = static_cast(time - mLastTime); + mLastTime = time; + + float brightness = 1.0f; + float cycle_time; + float time_distortion; + + const float pi = 3.14159265359; + + if(mType == LT_Pulse || mType == LT_PulseSlow) + { + cycle_time = 2.0f * pi; + time_distortion = 20.0f; + } + else + { + static const float fa = 0.785398f; + static const float phase_wavelength = 120.0f * pi / fa; + + cycle_time = 500.0f; + mPhase = std::fmod(mPhase + dt, phase_wavelength); + time_distortion = flickerFrequency(mPhase); + } + + mDeltaCount += mDirection*dt*time_distortion; + if(mDirection > 0 && mDeltaCount > +cycle_time) + { + mDirection = -1.0f; + mDeltaCount = 2.0f*cycle_time - mDeltaCount; + } + if(mDirection < 0 && mDeltaCount < -cycle_time) + { + mDirection = +1.0f; + mDeltaCount = -2.0f*cycle_time - mDeltaCount; + } + + static const float fast = 4.0f/1.0f; + static const float slow = 1.0f/1.0f; + + // These formulas are just guesswork, but they work pretty well + if(mType == LT_Normal) + { + // Less than 1/255 light modifier for a constant light: + brightness = 1.0f + flickerAmplitude(mDeltaCount*slow)/255.0f; + } + else if(mType == LT_Flicker) + brightness = 0.75f + flickerAmplitude(mDeltaCount*fast)*0.25f; + else if(mType == LT_FlickerSlow) + brightness = 0.75f + flickerAmplitude(mDeltaCount*slow)*0.25f; + else if(mType == LT_Pulse) + brightness = 1.0f + pulseAmplitude(mDeltaCount*fast)*0.25f; + else if(mType == LT_PulseSlow) + brightness = 1.0f + pulseAmplitude(mDeltaCount*slow)*0.25f; + + static_cast(node)->getLight()->setDiffuse(mDiffuseColor * brightness); + } + + void LightController::setDiffuse(osg::Vec4f color) + { + mDiffuseColor = color; + } + +} diff --git a/libs/openengine/CMakeLists.txt b/libs/openengine/CMakeLists.txt index 3542becf6..e9a1bcd9e 100644 --- a/libs/openengine/CMakeLists.txt +++ b/libs/openengine/CMakeLists.txt @@ -23,12 +23,7 @@ set(OENGINE_BULLET bullet/trace.h ) -set(OENGINE_MISC - misc/rng.cpp - misc/rng.hpp -) - -set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_BULLET} ${OENGINE_MISC}) +set(OENGINE_ALL ${OENGINE_GUI} ${OENGINE_BULLET}) set(OENGINE_LIBRARY "oengine") set(OENGINE_LIBRARY ${OENGINE_LIBRARY} PARENT_SCOPE) From 98ea184ddae63b4a55ff2b9893af9ede371f3023 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 27 Nov 2015 21:40:36 +0100 Subject: [PATCH 356/441] Do not assert() for invalid land data in plugins (Bug #3037) The resizing of LTEX store to the correct number of plugins was done in the load() method, but the load method won't be called if a plugin contains LAND records but doesn't contain LTEX records. For such plugins the Store::search() function would then fail an assertion. (cherry picked from commit 4687c4baad4397d55d42ab37b225dbbf25cf8781) # Conflicts: # apps/openmw/mwworld/store.cpp # apps/openmw/mwworld/store.hpp --- apps/openmw/mwworld/esmstore.cpp | 6 + apps/openmw/mwworld/store.cpp | 239 +++++++++++++++++++++++++++++++ apps/openmw/mwworld/store.hpp | 8 +- 3 files changed, 250 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 2a302e082..91233cf70 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -31,6 +31,12 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) ESM::Dialogue *dialogue = 0; + // Land texture loading needs to use a separate internal store for each plugin. + // We set the number of plugins here to avoid continual resizes during loading, + // and so we can properly verify if valid plugin indices are being passed to the + // LandTexture Store retrieval methods. + mLandTextures.resize(esm.getGlobalReaderList()->size()); + /// \todo Move this to somewhere else. ESMReader? // Cache parent esX files by tracking their indices in the global list of // all files/readers used by the engine. This will greaty accelerate diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 767a33b13..925e9c605 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -96,6 +96,245 @@ void Store::load(ESM::ESMReader &esm) wipecell->mLeasedRefs.erase(it_lease); *itold = *it; } + ++sharedIter; + } + mStatic.erase(it); + } + + return true; + } + + template + bool Store::erase(const std::string &id) + { + std::string key = Misc::StringUtils::lowerCase(id); + typename Dynamic::iterator it = mDynamic.find(key); + if (it == mDynamic.end()) { + return false; + } + mDynamic.erase(it); + + // have to reinit the whole shared part + assert(mShared.size() >= mStatic.size()); + mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); + for (it = mDynamic.begin(); it != mDynamic.end(); ++it) { + mShared.push_back(&it->second); + } + return true; + } + template + bool Store::erase(const T &item) + { + return erase(item.mId); + } + template + void Store::write (ESM::ESMWriter& writer, Loading::Listener& progress) const + { + for (typename Dynamic::const_iterator iter (mDynamic.begin()); iter!=mDynamic.end(); + ++iter) + { + writer.startRecord (T::sRecordId); + iter->second.save (writer); + writer.endRecord (T::sRecordId); + } + } + template + RecordId Store::read(ESM::ESMReader& reader) + { + T record; + bool isDeleted = false; + + record.load (reader, isDeleted); + insert (record); + + return RecordId(record.mId, isDeleted); + } + + // LandTexture + //========================================================================= + Store::Store() + { + mStatic.push_back(LandTextureList()); + LandTextureList <exl = mStatic[0]; + // More than enough to hold Morrowind.esm. Extra lists for plugins will we + // added on-the-fly in a different method. + ltexl.reserve(128); + } + const ESM::LandTexture *Store::search(size_t index, size_t plugin) const + { + assert(plugin < mStatic.size()); + const LandTextureList <exl = mStatic[plugin]; + + if (index >= ltexl.size()) + return NULL; + return <exl[index]; + } + const ESM::LandTexture *Store::find(size_t index, size_t plugin) const + { + const ESM::LandTexture *ptr = search(index, plugin); + if (ptr == 0) { + std::ostringstream msg; + msg << "Land texture with index " << index << " not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + size_t Store::getSize() const + { + return mStatic.size(); + } + size_t Store::getSize(size_t plugin) const + { + assert(plugin < mStatic.size()); + return mStatic[plugin].size(); + } + RecordId Store::load(ESM::ESMReader &esm, size_t plugin) + { + ESM::LandTexture lt; + bool isDeleted = false; + + lt.load(esm, isDeleted); + + assert(plugin < mStatic.size()); + + LandTextureList <exl = mStatic[plugin]; + if(lt.mIndex + 1 > (int)ltexl.size()) + ltexl.resize(lt.mIndex+1); + + // Store it + ltexl[lt.mIndex] = lt; + + return RecordId(lt.mId, isDeleted); + } + RecordId Store::load(ESM::ESMReader &esm) + { + return load(esm, esm.getIndex()); + } + Store::iterator Store::begin(size_t plugin) const + { + assert(plugin < mStatic.size()); + return mStatic[plugin].begin(); + } + Store::iterator Store::end(size_t plugin) const + { + assert(plugin < mStatic.size()); + return mStatic[plugin].end(); + } + void Store::resize(size_t num) + { + if (mStatic.size() < num) + mStatic.resize(num); + } + + // Land + //========================================================================= + Store::~Store() + { + for (std::vector::const_iterator it = + mStatic.begin(); it != mStatic.end(); ++it) + { + delete *it; + } + + } + size_t Store::getSize() const + { + return mStatic.size(); + } + Store::iterator Store::begin() const + { + return iterator(mStatic.begin()); + } + Store::iterator Store::end() const + { + return iterator(mStatic.end()); + } + ESM::Land *Store::search(int x, int y) const + { + ESM::Land land; + land.mX = x, land.mY = y; + + std::vector::const_iterator it = + std::lower_bound(mStatic.begin(), mStatic.end(), &land, Compare()); + + if (it != mStatic.end() && (*it)->mX == x && (*it)->mY == y) { + return const_cast(*it); + } + return 0; + } + ESM::Land *Store::find(int x, int y) const + { + ESM::Land *ptr = search(x, y); + if (ptr == 0) { + std::ostringstream msg; + msg << "Land at (" << x << ", " << y << ") not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + RecordId Store::load(ESM::ESMReader &esm) + { + ESM::Land *ptr = new ESM::Land(); + bool isDeleted = false; + + ptr->load(esm, isDeleted); + + // Same area defined in multiple plugins? -> last plugin wins + // Can't use search() because we aren't sorted yet - is there any other way to speed this up? + for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) + { + if ((*it)->mX == ptr->mX && (*it)->mY == ptr->mY) + { + delete *it; + mStatic.erase(it); + break; + } + } + + mStatic.push_back(ptr); + + return RecordId("", isDeleted); + } + void Store::setUp() + { + std::sort(mStatic.begin(), mStatic.end(), Compare()); + } + + + // Cell + //========================================================================= + + const ESM::Cell *Store::search(const ESM::Cell &cell) const + { + if (cell.isExterior()) { + return search(cell.getGridX(), cell.getGridY()); + } + return search(cell.mName); + } + void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) + { + //Handling MovedCellRefs, there is no way to do it inside loadcell + while (esm.isNextSub("MVRF")) { + ESM::CellRef ref; + ESM::MovedCellRef cMRef; + cell->getNextMVRF(esm, cMRef); + + ESM::Cell *cellAlt = const_cast(searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); + + // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following + // implementation when the oher implementation works as well. + bool deleted = false; + cell->getNextRef(esm, ref, deleted); + + // Add data required to make reference appear in the correct cell. + // We should not need to test for duplicates, as this part of the code is pre-cell merge. + cell->mMovedRefs.push_back(cMRef); + // But there may be duplicates here! + if (!deleted) + { + ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefNum); + if (iter == cellAlt->mLeasedRefs.end()) + cellAlt->mLeasedRefs.push_back(ref); else oldcell->mMovedRefs.push_back(*it); } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index ff0ca0159..5cc66981c 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -434,9 +434,11 @@ namespace MWWorld assert(plugin < mStatic.size()); const LandTextureList <exl = mStatic[plugin]; - assert(index < ltexl.size()); - return <exl.at(index); - } + /// Resize the internal store to hold at least \a num plugins. + void resize(size_t num); + + size_t getSize() const; + size_t getSize(size_t plugin) const; const ESM::LandTexture *find(size_t index, size_t plugin) const { const ESM::LandTexture *ptr = search(index, plugin); From f456174af8e4452e0d38182520f033e6898bd67f Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 7 Dec 2015 22:49:15 +0100 Subject: [PATCH 357/441] Rename to lowerCaseInPlace (cherry picked from commit 07b064f61682096aef2f5f7838cd064847f5d24e) # Conflicts: # apps/opencs/model/world/commanddispatcher.cpp # apps/opencs/model/world/regionmap.cpp # apps/opencs/model/world/scriptcontext.cpp # apps/openmw/mwrender/animation.cpp # apps/openmw/mwscript/statsextensions.cpp # apps/openmw/mwworld/store.cpp # components/misc/stringops.hpp --- apps/essimporter/converter.hpp | 2 +- apps/mwiniimporter/importer.cpp | 2 +- apps/opencs/model/world/commanddispatcher.cpp | 2 +- apps/opencs/model/world/regionmap.cpp | 2 +- apps/opencs/model/world/scriptcontext.cpp | 2 +- apps/openmw/mwclass/container.cpp | 4 +- apps/openmw/mwclass/door.cpp | 4 +- apps/openmw/mwgui/formatting.cpp | 4 +- apps/openmw/mwgui/itemmodel.cpp | 6 +-- apps/openmw/mwmechanics/activespells.cpp | 8 +--- apps/openmw/mwmechanics/activespells.hpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 4 +- apps/openmw/mwrender/animation.cpp | 8 +--- apps/openmw/mwscript/cellextensions.cpp | 2 +- apps/openmw/mwscript/guiextensions.cpp | 4 +- apps/openmw/mwscript/statsextensions.cpp | 20 ++++---- apps/openmw/mwworld/cellstore.cpp | 2 +- apps/openmw/mwworld/globals.cpp | 2 +- apps/openmw/mwworld/store.cpp | 46 ++++++++++++++++++- components/misc/resourcehelpers.cpp | 2 +- components/misc/stringops.hpp | 5 +- 21 files changed, 85 insertions(+), 48 deletions(-) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 61155c1ea..467679e20 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -395,7 +395,7 @@ public: virtual void read(ESM::ESMReader &esm) { std::string itemid = esm.getHNString("NAME"); - Misc::StringUtils::toLower(itemid); + Misc::StringUtils::lowerCaseInPlace(itemid); while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) { diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 479f8cba2..e1534ce61 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -847,7 +847,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { std::string filetype(entry->substr(entry->length()-3)); - Misc::StringUtils::toLower(filetype); + Misc::StringUtils::lowerCaseInPlace(filetype); if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { boost::filesystem::path filepath(gameFilesDir); diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index 0b1af0e84..a1fc980eb 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -93,7 +93,7 @@ void CSMWorld::CommandDispatcher::setEditLock (bool locked) void CSMWorld::CommandDispatcher::setSelection (const std::vector& selection) { mSelection = selection; - std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::toLower); + std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::lowerCaseInPlace); std::sort (mSelection.begin(), mSelection.end()); } diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 10c67c909..6dbbac97f 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -168,7 +168,7 @@ void CSMWorld::RegionMap::updateRegions (const std::vector& regions { std::vector regions2 (regions); - std::for_each (regions2.begin(), regions2.end(), &Misc::StringUtils::lowerCase); + std::for_each (regions2.begin(), regions2.end(), Misc::StringUtils::lowerCaseInPlace); std::sort (regions2.begin(), regions2.end()); for (std::map::const_iterator iter (mMap.begin()); diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp index f644ad37a..344ae322e 100644 --- a/apps/opencs/model/world/scriptcontext.cpp +++ b/apps/opencs/model/world/scriptcontext.cpp @@ -93,7 +93,7 @@ bool CSMWorld::ScriptContext::isId (const std::string& name) const { mIds = mData.getIds(); - std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::toLower); + std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::lowerCaseInPlace); std::sort (mIds.begin(), mIds.end()); mIdsUpdated = true; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 9d2e34ea3..cccb21e8f 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -146,11 +146,11 @@ namespace MWClass // make key id lowercase std::string keyId = ptr.getCellRef().getKey(); - Misc::StringUtils::toLower(keyId); + Misc::StringUtils::lowerCaseInPlace(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::toLower(refId); + Misc::StringUtils::lowerCaseInPlace(refId); if (refId == keyId) { hasKey = true; diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index ab9cfa289..83f2680b7 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -113,11 +113,11 @@ namespace MWClass // make key id lowercase std::string keyId = ptr.getCellRef().getKey(); - Misc::StringUtils::toLower(keyId); + Misc::StringUtils::lowerCaseInPlace(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::toLower(refId); + Misc::StringUtils::lowerCaseInPlace(refId); if (refId == keyId) { hasKey = true; diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 761a89ca6..85cef8911 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -129,7 +129,7 @@ namespace MWGui size_t tagNameEndPos = tag.find(' '); mAttributes.clear(); mTag = tag.substr(0, tagNameEndPos); - Misc::StringUtils::toLower(mTag); + Misc::StringUtils::lowerCaseInPlace(mTag); if (mTag.empty()) return; @@ -151,7 +151,7 @@ namespace MWGui return; std::string key = tag.substr(0, sepPos); - Misc::StringUtils::toLower(key); + Misc::StringUtils::lowerCaseInPlace(key); tag.erase(0, sepPos+1); std::string value; diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 9fce6e84d..64a23c956 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -35,7 +35,7 @@ namespace MWGui { const ESM::GameSetting ¤tSetting = *currentIteration; std::string currentGMSTID = currentSetting.mId; - Misc::StringUtils::toLower(currentGMSTID); + Misc::StringUtils::lowerCaseInPlace(currentGMSTID); // Don't bother checking this GMST if it's not a sMagicBound* one. const std::string& toFind = "smagicbound"; @@ -44,7 +44,7 @@ namespace MWGui // All sMagicBound* GMST's should be of type string std::string currentGMSTValue = currentSetting.getString(); - Misc::StringUtils::toLower(currentGMSTValue); + Misc::StringUtils::lowerCaseInPlace(currentGMSTValue); boundItemIDCache.insert(currentGMSTValue); } @@ -52,7 +52,7 @@ namespace MWGui // Perform bound item check and assign the Flag_Bound bit if it passes std::string tempItemID = base.getCellRef().getRefId(); - Misc::StringUtils::toLower(tempItemID); + Misc::StringUtils::lowerCaseInPlace(tempItemID); if (boundItemIDCache.count(tempItemID) != 0) mFlags |= Flag_Bound; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 7068310a0..6a247622c 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -132,15 +132,11 @@ namespace MWMechanics return scaledDuration-usedUp; } - bool ActiveSpells::isSpellActive(std::string id) const + bool ActiveSpells::isSpellActive(const std::string& id) const { - Misc::StringUtils::toLower(id); for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) { - std::string left = iter->first; - Misc::StringUtils::toLower(left); - - if (iter->first == id) + if (Misc::StringUtils::ciEqual(iter->first, id)) return true; } return false; diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 2a4d75d40..3842ee61c 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -97,7 +97,7 @@ namespace MWMechanics /// Remove all spells void clear(); - bool isSpellActive (std::string id) const; + bool isSpellActive (const std::string& id) const; ///< case insensitive const MagicEffects& getMagicEffects() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 2bf23b586..cc0ddc72a 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -607,7 +607,7 @@ namespace MWMechanics int rank = 0; std::string npcFaction = ptr.getClass().getPrimaryFaction(ptr); - Misc::StringUtils::toLower(npcFaction); + Misc::StringUtils::lowerCaseInPlace(npcFaction); if (playerStats.getFactionRanks().find(npcFaction) != playerStats.getFactionRanks().end()) { @@ -1045,7 +1045,7 @@ namespace MWMechanics owner.first = ownerCellRef->getFaction(); owner.second = true; } - Misc::StringUtils::toLower(owner.first); + Misc::StringUtils::lowerCaseInPlace(owner.first); if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) mStolenItems[Misc::StringUtils::lowerCase(item.getClass().getId(item))][owner] += count; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6dcd92b15..05565ebc3 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -430,12 +430,8 @@ NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::Tex NifOgre::TextKeyMap::const_iterator iter(keys.begin()); for(;iter != keys.end();++iter) { - if(iter->second.compare(0, groupname.size(), groupname) == 0 && - iter->second.compare(groupname.size(), 2, ": ") == 0) - break; - } - return iter; -} + std::string kfname = model; + Misc::StringUtils::lowerCaseInPlace(kfname); bool Animation::hasAnimation(const std::string &anim) diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 43d213c5a..2d69e485f 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -120,7 +120,7 @@ namespace MWScript const MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); std::string current = MWBase::Environment::get().getWorld()->getCellName(cell); - Misc::StringUtils::toLower(current); + Misc::StringUtils::lowerCaseInPlace(current); bool match = current.length()>=name.length() && current.substr (0, name.length())==name; diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index f48360c04..48b17917e 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -114,7 +114,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { std::string cell = (runtime.getStringLiteral (runtime[0].mInteger)); - ::Misc::StringUtils::toLower(cell); + ::Misc::StringUtils::lowerCaseInPlace(cell); runtime.pop(); // "Will match complete or partial cells, so ShowMap, "Vivec" will show cells Vivec and Vivec, Fred's House as well." @@ -127,7 +127,7 @@ namespace MWScript for (; it != cells.extEnd(); ++it) { std::string name = it->mName; - ::Misc::StringUtils::toLower(name); + ::Misc::StringUtils::lowerCaseInPlace(name); if (name.find(cell) != std::string::npos) MWBase::Environment::get().getWindowManager()->addVisitedLocation ( it->mName, diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 80f46e457..875a07374 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -542,7 +542,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -574,7 +574,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -613,7 +613,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -644,7 +644,7 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -755,7 +755,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); runtime.push ( @@ -790,7 +790,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats (player).setFactionReputation (factionId, value); @@ -824,7 +824,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats (player).setFactionReputation (factionId, @@ -869,11 +869,11 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); std::string race = runtime.getStringLiteral(runtime[0].mInteger); - ::Misc::StringUtils::toLower(race); + ::Misc::StringUtils::lowerCaseInPlace(race); runtime.pop(); std::string npcRace = ptr.get()->mBase->mRace; - ::Misc::StringUtils::toLower(npcRace); + ::Misc::StringUtils::lowerCaseInPlace(npcRace); runtime.push (npcRace == race); } @@ -910,7 +910,7 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(factionID!="") { diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 67de77ceb..0e392f055 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -540,7 +540,7 @@ namespace MWWorld void CellStore::loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store) { - Misc::StringUtils::toLower (ref.mRefID); + Misc::StringUtils::lowerCaseInPlace (ref.mRefID); switch (store.find (ref.mRefID)) { diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index 4a406613d..acc06b28a 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -96,7 +96,7 @@ namespace MWWorld // This readRecord() method is used when reading a saved game. // Deleted globals can't appear there, so isDeleted will be ignored here. global.load(reader, isDeleted); - Misc::StringUtils::toLower(global.mId); + Misc::StringUtils::lowerCaseInPlace(global.mId); Collection::iterator iter = mVariables.find (global.mId); if (iter!=mVariables.end()) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 925e9c605..8fa5b58cb 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -66,7 +66,51 @@ void Store::load(ESM::ESMReader &esm) // spawn a new cell cell.loadCell(esm, true); - mInt[idLower] = cell; + record.load(esm, isDeleted); + Misc::StringUtils::lowerCaseInPlace(record.mId); + + std::pair inserted = mStatic.insert(std::make_pair(record.mId, record)); + if (inserted.second) + mShared.push_back(&inserted.first->second); + else + inserted.first->second = record; + + return RecordId(record.mId, isDeleted); + } + template + void Store::setUp() + { + } + + template + typename Store::iterator Store::begin() const + { + return mShared.begin(); + } + template + typename Store::iterator Store::end() const + { + return mShared.end(); + } + + template + size_t Store::getSize() const + { + return mShared.size(); + } + + template + int Store::getDynamicSize() const + { + return mDynamic.size(); + } + template + void Store::listIdentifier(std::vector &list) const + { + list.reserve(list.size() + getSize()); + typename std::vector::const_iterator it = mShared.begin(); + for (; it != mShared.end(); ++it) { + list.push_back((*it)->mId); } } else diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index dc08b352a..1c6834e67 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -49,7 +49,7 @@ std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLev std::string prefix2 = topLevelDirectory + '/'; std::string correctedPath = resPath; - Misc::StringUtils::toLower(correctedPath); + Misc::StringUtils::lowerCaseInPlace(correctedPath); // Apparently, leading separators are allowed while (correctedPath.size() && (correctedPath[0] == '/' || correctedPath[0] == '\\')) diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 1f6da37c7..f54018a87 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -70,7 +70,7 @@ public: } /// Transforms input string to lower case w/o copy - static std::string &toLower(std::string &inout) { + static void lowerCaseInPlace(std::string &inout) { for (unsigned int i=0; i Date: Fri, 5 Oct 2018 21:54:29 +1000 Subject: [PATCH 358/441] Resolved conflicts and build fixes. --- apps/essimporter/converter.hpp | 2 +- apps/openmw/mwclass/static.cpp | 5 +- apps/openmw/mwmechanics/character.cpp | 8 +- apps/openmw/mwrender/animation.cpp | 10 +- apps/openmw/mwrender/sky.cpp | 4 +- apps/openmw/mwworld/store.cpp | 972 ++++++++++++++++++++--- apps/openmw/mwworld/store.hpp | 900 ++++----------------- components/fontloader/fontloader.cpp | 13 +- components/misc/stringops.hpp | 1 - components/nifbullet/bulletnifloader.cpp | 2 +- components/nifogre/ogrenifloader.cpp | 16 +- components/nifogre/particles.cpp | 4 +- libs/openengine/CMakeLists.txt | 2 +- libs/openengine/ogre/selectionbuffer.cpp | 4 +- 14 files changed, 1049 insertions(+), 894 deletions(-) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 467679e20..a3fef7f32 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -378,7 +378,7 @@ public: bool isDeleted = false; faction.load(esm, isDeleted); - std::string id = Misc::StringUtils::toLower(faction.mId); + std::string id = Misc::StringUtils::lowerCase(faction.mId); for (std::map::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) { diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 7bfd4c76d..a5f8129a2 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -18,11 +18,8 @@ namespace MWClass void Static::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model, !ref->mBase->mPersistent); + renderingInterface.getObjects().insertModel(ptr, model); } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 399b51151..b90228b5b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -27,7 +27,7 @@ #include "creaturestats.hpp" #include "security.hpp" -#include +#include #include @@ -222,7 +222,7 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i while (mAnimation->hasAnimation(prefix + Ogre::StringConverter::toString(numAnims+1))) ++numAnims; - int roll = OEngine::Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] + int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] if (num) *num = roll; return prefix + Ogre::StringConverter::toString(roll); @@ -831,7 +831,7 @@ bool CharacterController::updateCreatureState() } if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation { - int roll = OEngine::Misc::Rng::rollDice(3); // [0, 2] + int roll = Misc::Rng::rollDice(3); // [0, 2] if (roll == 0) mCurrentWeapon = "attack1"; else if (roll == 1) @@ -1127,7 +1127,7 @@ bool CharacterController::updateWeaponState() // most creatures don't actually have an attack wind-up animation, so use a uniform random value // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. - attackStrength = std::min(1.f, 0.1f + OEngine::Misc::Rng::rollClosedProbability()); + attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); } if(mWeaponType != WeapType_Crossbow && mWeaponType != WeapType_BowAndArrow) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 05565ebc3..8b3f6a4ea 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -249,7 +249,7 @@ void Animation::addAnimSource(const std::string &model) return; std::string kfname = model; - Misc::StringUtils::toLower(kfname); + Misc::StringUtils::lowerCaseInPlace(kfname); if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) kfname.replace(kfname.size()-4, 4, ".kf"); @@ -430,8 +430,12 @@ NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::Tex NifOgre::TextKeyMap::const_iterator iter(keys.begin()); for(;iter != keys.end();++iter) { - std::string kfname = model; - Misc::StringUtils::lowerCaseInPlace(kfname); + if(iter->second.compare(0, groupname.size(), groupname) == 0 && + iter->second.compare(groupname.size(), 2, ": ") == 0) + break; + } + return iter; +} bool Animation::hasAnimation(const std::string &anim) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 2aab1c0cc..58362756d 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -466,8 +466,8 @@ void SkyManager::updateRain(float dt) // TODO: handle rain settings from Morrowind.ini const float rangeRandom = 100; - float xOffs = OEngine::Misc::Rng::rollProbability() * rangeRandom - (rangeRandom / 2); - float yOffs = OEngine::Misc::Rng::rollProbability() * rangeRandom - (rangeRandom / 2); + float xOffs = Misc::Rng::rollProbability() * rangeRandom - (rangeRandom / 2); + float yOffs = Misc::Rng::rollProbability() * rangeRandom - (rangeRandom / 2); // Create a separate node to control the offset, since a node with setInheritOrientation(false) will still // consider the orientation of the parent node for its position, just not for its orientation diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 8fa5b58cb..adaf8056a 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -3,68 +3,189 @@ #include -namespace MWWorld { +#include +#include +#include -void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) +namespace { - //Handling MovedCellRefs, there is no way to do it inside loadcell - while (esm.isNextSub("MVRF")) { - ESM::CellRef ref; - ESM::MovedCellRef cMRef; - cell->getNextMVRF(esm, cMRef); - - ESM::Cell *cellAlt = const_cast(searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); - - // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following - // implementation when the oher implementation works as well. - bool deleted = false; - cell->getNextRef(esm, ref, deleted); - - // Add data required to make reference appear in the correct cell. - // We should not need to test for duplicates, as this part of the code is pre-cell merge. - cell->mMovedRefs.push_back(cMRef); - // But there may be duplicates here! - if (!deleted) + template + class GetRecords + { + const std::string mFind; + std::vector *mRecords; + + public: + GetRecords(const std::string &str, std::vector *records) + : mFind(Misc::StringUtils::lowerCase(str)), mRecords(records) + { } + + void operator()(const T *item) { - ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefNum); - if (iter == cellAlt->mLeasedRefs.end()) - cellAlt->mLeasedRefs.push_back(ref); - else - *iter = ref; + if(Misc::StringUtils::ciCompareLen(mFind, item->mId, mFind.size()) == 0) + mRecords->push_back(item); } - } + }; + + struct Compare + { + bool operator()(const ESM::Land *x, const ESM::Land *y) { + if (x->mX == y->mX) { + return x->mY < y->mY; + } + return x->mX < y->mX; + } + }; } -void Store::load(ESM::ESMReader &esm) +namespace MWWorld { - // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, - // and we merge all this data into one Cell object. However, we can't simply search for the cell id, - // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they - // are not available until both cells have been loaded at least partially! - - // All cells have a name record, even nameless exterior cells. - ESM::Cell cell; - cell.loadName(esm); - std::string idLower = Misc::StringUtils::lowerCase(cell.mName); - - // Load the (x,y) coordinates of the cell, if it is an exterior cell, - // so we can find the cell we need to merge with - cell.loadData(esm); - - if(cell.mData.mFlags & ESM::Cell::Interior) - { - // Store interior cell by name, try to merge with existing parent data. - ESM::Cell *oldcell = const_cast(search(idLower)); - if (oldcell) { - // merge new cell into old cell - // push the new references on the list of references to manage (saveContext = true) - oldcell->mData = cell.mData; - oldcell->mName = cell.mName; // merge name just to be sure (ID will be the same, but case could have been changed) - oldcell->loadCell(esm, true); - } else + RecordId::RecordId(const std::string &id, bool isDeleted) + : mId(id), mIsDeleted(isDeleted) + {} + + template + IndexedStore::IndexedStore() + { + } + template + typename IndexedStore::iterator IndexedStore::begin() const + { + return mStatic.begin(); + } + template + typename IndexedStore::iterator IndexedStore::end() const + { + return mStatic.end(); + } + template + void IndexedStore::load(ESM::ESMReader &esm) + { + T record; + bool isDeleted = false; + + record.load(esm, isDeleted); + + // Try to overwrite existing record + std::pair ret = mStatic.insert(std::make_pair(record.mIndex, record)); + if (!ret.second) + ret.first->second = record; + } + template + int IndexedStore::getSize() const + { + return mStatic.size(); + } + template + void IndexedStore::setUp() + { + } + template + const T *IndexedStore::search(int index) const + { + typename Static::const_iterator it = mStatic.find(index); + if (it != mStatic.end()) + return &(it->second); + return NULL; + } + template + const T *IndexedStore::find(int index) const + { + const T *ptr = search(index); + if (ptr == 0) { + std::ostringstream msg; + msg << T::getRecordType() << " with index " << index << " not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + + // Need to instantiate these before they're used + template class IndexedStore; + template class IndexedStore; + + template + Store::Store() + { + } + + template + Store::Store(const Store& orig) + : mStatic(orig.mStatic) + { + } + + template + void Store::clearDynamic() + { + // remove the dynamic part of mShared + assert(mShared.size() >= mStatic.size()); + mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); + mDynamic.clear(); + } + + template + const T *Store::search(const std::string &id) const + { + T item; + item.mId = Misc::StringUtils::lowerCase(id); + + typename Dynamic::const_iterator dit = mDynamic.find(item.mId); + if (dit != mDynamic.end()) { + return &dit->second; + } + + typename std::map::const_iterator it = mStatic.find(item.mId); + + if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + return &(it->second); + } + + return 0; + } + template + bool Store::isDynamic(const std::string &id) const + { + typename Dynamic::const_iterator dit = mDynamic.find(id); + return (dit != mDynamic.end()); + } + template + const T *Store::searchRandom(const std::string &id) const + { + std::vector results; + std::for_each(mShared.begin(), mShared.end(), GetRecords(id, &results)); + if(!results.empty()) + return results[Misc::Rng::rollDice(results.size())]; + return NULL; + } + template + const T *Store::find(const std::string &id) const + { + const T *ptr = search(id); + if (ptr == 0) { + std::ostringstream msg; + msg << T::getRecordType() << " '" << id << "' not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + template + const T *Store::findRandom(const std::string &id) const + { + const T *ptr = searchRandom(id); + if(ptr == 0) { - // spawn a new cell - cell.loadCell(esm, true); + std::ostringstream msg; + msg << T::getRecordType() << " starting with '"< + RecordId Store::load(ESM::ESMReader &esm) + { + T record; + bool isDeleted = false; record.load(esm, isDeleted); Misc::StringUtils::lowerCaseInPlace(record.mId); @@ -85,7 +206,7 @@ void Store::load(ESM::ESMReader &esm) template typename Store::iterator Store::begin() const { - return mShared.begin(); + return mShared.begin(); } template typename Store::iterator Store::end() const @@ -113,32 +234,51 @@ void Store::load(ESM::ESMReader &esm) list.push_back((*it)->mId); } } - else - { - // Store exterior cells by grid position, try to merge with existing parent data. - ESM::Cell *oldcell = const_cast(search(cell.getGridX(), cell.getGridY())); - if (oldcell) { - // merge new cell into old cell - oldcell->mData = cell.mData; - oldcell->mName = cell.mName; - oldcell->loadCell(esm, false); - - // handle moved ref (MVRF) subrecords - handleMovedCellRefs (esm, &cell); - - // push the new references on the list of references to manage - oldcell->postLoad(esm); - - // merge lists of leased references, use newer data in case of conflict - for (ESM::MovedCellRefTracker::const_iterator it = cell.mMovedRefs.begin(); it != cell.mMovedRefs.end(); ++it) { - // remove reference from current leased ref tracker and add it to new cell - ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefNum); - if (itold != oldcell->mMovedRefs.end()) { - ESM::MovedCellRef target0 = *itold; - ESM::Cell *wipecell = const_cast(search(target0.mTarget[0], target0.mTarget[1])); - ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefNum); - wipecell->mLeasedRefs.erase(it_lease); - *itold = *it; + template + T *Store::insert(const T &item) + { + std::string id = Misc::StringUtils::lowerCase(item.mId); + std::pair result = + mDynamic.insert(std::pair(id, item)); + T *ptr = &result.first->second; + if (result.second) { + mShared.push_back(ptr); + } else { + *ptr = item; + } + return ptr; + } + template + T *Store::insertStatic(const T &item) + { + std::string id = Misc::StringUtils::lowerCase(item.mId); + std::pair result = + mStatic.insert(std::pair(id, item)); + T *ptr = &result.first->second; + if (result.second) { + mShared.push_back(ptr); + } else { + *ptr = item; + } + return ptr; + } + template + bool Store::eraseStatic(const std::string &id) + { + T item; + item.mId = Misc::StringUtils::lowerCase(id); + + typename std::map::iterator it = mStatic.find(item.mId); + + if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + // delete from the static part of mShared + typename std::vector::iterator sharedIter = mShared.begin(); + typename std::vector::iterator end = sharedIter + mStatic.size(); + + while (sharedIter != mShared.end() && sharedIter != end) { + if((*sharedIter)->mId == item.mId) { + mShared.erase(sharedIter); + break; } ++sharedIter; } @@ -166,11 +306,13 @@ void Store::load(ESM::ESMReader &esm) } return true; } + template bool Store::erase(const T &item) { return erase(item.mId); } + template void Store::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { @@ -182,6 +324,7 @@ void Store::load(ESM::ESMReader &esm) writer.endRecord (T::sRecordId); } } + template RecordId Store::read(ESM::ESMReader& reader) { @@ -204,6 +347,7 @@ void Store::load(ESM::ESMReader &esm) // added on-the-fly in a different method. ltexl.reserve(128); } + const ESM::LandTexture *Store::search(size_t index, size_t plugin) const { assert(plugin < mStatic.size()); @@ -213,6 +357,7 @@ void Store::load(ESM::ESMReader &esm) return NULL; return <exl[index]; } + const ESM::LandTexture *Store::find(size_t index, size_t plugin) const { const ESM::LandTexture *ptr = search(index, plugin); @@ -223,15 +368,18 @@ void Store::load(ESM::ESMReader &esm) } return ptr; } + size_t Store::getSize() const { return mStatic.size(); } + size_t Store::getSize(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].size(); } + RecordId Store::load(ESM::ESMReader &esm, size_t plugin) { ESM::LandTexture lt; @@ -250,26 +398,30 @@ void Store::load(ESM::ESMReader &esm) return RecordId(lt.mId, isDeleted); } + RecordId Store::load(ESM::ESMReader &esm) { return load(esm, esm.getIndex()); } + Store::iterator Store::begin(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].begin(); } + Store::iterator Store::end(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].end(); } + void Store::resize(size_t num) { if (mStatic.size() < num) mStatic.resize(num); } - + // Land //========================================================================= Store::~Store() @@ -279,20 +431,23 @@ void Store::load(ESM::ESMReader &esm) { delete *it; } - } + size_t Store::getSize() const { return mStatic.size(); } + Store::iterator Store::begin() const { return iterator(mStatic.begin()); } + Store::iterator Store::end() const { return iterator(mStatic.end()); } + ESM::Land *Store::search(int x, int y) const { ESM::Land land; @@ -306,6 +461,7 @@ void Store::load(ESM::ESMReader &esm) } return 0; } + ESM::Land *Store::find(int x, int y) const { ESM::Land *ptr = search(x, y); @@ -316,6 +472,7 @@ void Store::load(ESM::ESMReader &esm) } return ptr; } + RecordId Store::load(ESM::ESMReader &esm) { ESM::Land *ptr = new ESM::Land(); @@ -339,12 +496,12 @@ void Store::load(ESM::ESMReader &esm) return RecordId("", isDeleted); } + void Store::setUp() { std::sort(mStatic.begin(), mStatic.end(), Compare()); } - // Cell //========================================================================= @@ -355,6 +512,7 @@ void Store::load(ESM::ESMReader &esm) } return search(cell.mName); } + void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) { //Handling MovedCellRefs, there is no way to do it inside loadcell @@ -380,31 +538,643 @@ void Store::load(ESM::ESMReader &esm) if (iter == cellAlt->mLeasedRefs.end()) cellAlt->mLeasedRefs.push_back(ref); else - oldcell->mMovedRefs.push_back(*it); + *iter = ref; } + } + } + + const ESM::Cell *Store::search(const std::string &id) const + { + ESM::Cell cell; + cell.mName = Misc::StringUtils::lowerCase(id); + + std::map::const_iterator it = mInt.find(cell.mName); + + if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) { + return &(it->second); + } + + DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName); + if (dit != mDynamicInt.end()) { + return &dit->second; + } + + return 0; + } + + const ESM::Cell *Store::search(int x, int y) const + { + ESM::Cell cell; + cell.mData.mX = x, cell.mData.mY = y; + + std::pair key(x, y); + DynamicExt::const_iterator it = mExt.find(key); + if (it != mExt.end()) { + return &(it->second); + } + + DynamicExt::const_iterator dit = mDynamicExt.find(key); + if (dit != mDynamicExt.end()) { + return &dit->second; + } - // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a - // reference to this cell, so the list for the new cell should be empty. The list for oldcell, - // however, could have leased refs in it and so should be kept. - } else + return 0; + } + + const ESM::Cell *Store::searchOrCreate(int x, int y) + { + std::pair key(x, y); + DynamicExt::const_iterator it = mExt.find(key); + if (it != mExt.end()) { + return &(it->second); + } + + DynamicExt::const_iterator dit = mDynamicExt.find(key); + if (dit != mDynamicExt.end()) { + return &dit->second; + } + + ESM::Cell newCell; + newCell.mData.mX = x; + newCell.mData.mY = y; + newCell.mData.mFlags = ESM::Cell::HasWater; + newCell.mAmbi.mAmbient = 0; + newCell.mAmbi.mSunlight = 0; + newCell.mAmbi.mFog = 0; + newCell.mAmbi.mFogDensity = 0; + return &mExt.insert(std::make_pair(key, newCell)).first->second; + } + + const ESM::Cell *Store::find(const std::string &id) const + { + const ESM::Cell *ptr = search(id); + if (ptr == 0) { + std::ostringstream msg; + msg << "Interior cell '" << id << "' not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + + const ESM::Cell *Store::find(int x, int y) const + { + const ESM::Cell *ptr = search(x, y); + if (ptr == 0) { + std::ostringstream msg; + msg << "Exterior at (" << x << ", " << y << ") not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + + void Store::setUp() + { + typedef DynamicExt::iterator ExtIterator; + typedef std::map::iterator IntIterator; + + mSharedInt.clear(); + mSharedInt.reserve(mInt.size()); + for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) { + mSharedInt.push_back(&(it->second)); + } + + mSharedExt.clear(); + mSharedExt.reserve(mExt.size()); + for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) { + mSharedExt.push_back(&(it->second)); + } + } + + RecordId Store::load(ESM::ESMReader &esm) + { + // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, + // and we merge all this data into one Cell object. However, we can't simply search for the cell id, + // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they + // are not available until both cells have been loaded at least partially! + + // All cells have a name record, even nameless exterior cells. + ESM::Cell cell; + bool isDeleted = false; + + // Load the (x,y) coordinates of the cell, if it is an exterior cell, + // so we can find the cell we need to merge with + cell.loadNameAndData(esm, isDeleted); + std::string idLower = Misc::StringUtils::lowerCase(cell.mName); + + if(cell.mData.mFlags & ESM::Cell::Interior) { - // spawn a new cell - cell.loadCell(esm, false); + // Store interior cell by name, try to merge with existing parent data. + ESM::Cell *oldcell = const_cast(search(idLower)); + if (oldcell) { + // merge new cell into old cell + // push the new references on the list of references to manage (saveContext = true) + oldcell->mData = cell.mData; + oldcell->mName = cell.mName; // merge name just to be sure (ID will be the same, but case could have been changed) + oldcell->loadCell(esm, true); + } else + { + // spawn a new cell + cell.loadCell(esm, true); + + mInt[idLower] = cell; + } + } + else + { + // Store exterior cells by grid position, try to merge with existing parent data. + ESM::Cell *oldcell = const_cast(search(cell.getGridX(), cell.getGridY())); + if (oldcell) { + // merge new cell into old cell + oldcell->mData = cell.mData; + oldcell->mName = cell.mName; + oldcell->loadCell(esm, false); + + // handle moved ref (MVRF) subrecords + handleMovedCellRefs (esm, &cell); + + // push the new references on the list of references to manage + oldcell->postLoad(esm); + + // merge lists of leased references, use newer data in case of conflict + for (ESM::MovedCellRefTracker::const_iterator it = cell.mMovedRefs.begin(); it != cell.mMovedRefs.end(); ++it) { + // remove reference from current leased ref tracker and add it to new cell + ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefNum); + if (itold != oldcell->mMovedRefs.end()) { + ESM::MovedCellRef target0 = *itold; + ESM::Cell *wipecell = const_cast(search(target0.mTarget[0], target0.mTarget[1])); + ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefNum); + wipecell->mLeasedRefs.erase(it_lease); + *itold = *it; + } + else + oldcell->mMovedRefs.push_back(*it); + } + + // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a + // reference to this cell, so the list for the new cell should be empty. The list for oldcell, + // however, could have leased refs in it and so should be kept. + } else + { + // spawn a new cell + cell.loadCell(esm, false); - // handle moved ref (MVRF) subrecords - handleMovedCellRefs (esm, &cell); + // handle moved ref (MVRF) subrecords + handleMovedCellRefs (esm, &cell); - // push the new references on the list of references to manage - cell.postLoad(esm); + // push the new references on the list of references to manage + cell.postLoad(esm); - mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; + mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; + } } + + return RecordId(cell.mName, isDeleted); } -} -void Store::load(ESM::ESMReader &esm) -{ - load(esm, esm.getIndex()); -} + Store::iterator Store::intBegin() const + { + return iterator(mSharedInt.begin()); + } + + Store::iterator Store::intEnd() const + { + return iterator(mSharedInt.end()); + } + + Store::iterator Store::extBegin() const + { + return iterator(mSharedExt.begin()); + } + Store::iterator Store::extEnd() const + { + return iterator(mSharedExt.end()); + } + + const ESM::Cell *Store::searchExtByName(const std::string &id) const + { + ESM::Cell *cell = 0; + std::vector::const_iterator it = mSharedExt.begin(); + for (; it != mSharedExt.end(); ++it) { + if (Misc::StringUtils::ciEqual((*it)->mName, id)) { + if ( cell == 0 || + ( (*it)->mData.mX > cell->mData.mX ) || + ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) + { + cell = *it; + } + } + } + return cell; + } + + const ESM::Cell *Store::searchExtByRegion(const std::string &id) const + { + ESM::Cell *cell = 0; + std::vector::const_iterator it = mSharedExt.begin(); + for (; it != mSharedExt.end(); ++it) { + if (Misc::StringUtils::ciEqual((*it)->mRegion, id)) { + if ( cell == 0 || + ( (*it)->mData.mX > cell->mData.mX ) || + ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) + { + cell = *it; + } + } + } + return cell; + } + + size_t Store::getSize() const + { + return mSharedInt.size() + mSharedExt.size(); + } + + void Store::listIdentifier(std::vector &list) const + { + list.reserve(list.size() + mSharedInt.size()); + + std::vector::const_iterator it = mSharedInt.begin(); + for (; it != mSharedInt.end(); ++it) { + list.push_back((*it)->mName); + } + } + + ESM::Cell *Store::insert(const ESM::Cell &cell) + { + if (search(cell) != 0) { + std::ostringstream msg; + msg << "Failed to create "; + msg << ((cell.isExterior()) ? "exterior" : "interior"); + msg << " cell"; + + throw std::runtime_error(msg.str()); + } + ESM::Cell *ptr; + if (cell.isExterior()) { + std::pair key(cell.getGridX(), cell.getGridY()); + + // duplicate insertions are avoided by search(ESM::Cell &) + std::pair result = + mDynamicExt.insert(std::make_pair(key, cell)); + + ptr = &result.first->second; + mSharedExt.push_back(ptr); + } else { + std::string key = Misc::StringUtils::lowerCase(cell.mName); + + // duplicate insertions are avoided by search(ESM::Cell &) + std::pair result = + mDynamicInt.insert(std::make_pair(key, cell)); + + ptr = &result.first->second; + mSharedInt.push_back(ptr); + } + return ptr; + } + + bool Store::erase(const ESM::Cell &cell) + { + if (cell.isExterior()) { + return erase(cell.getGridX(), cell.getGridY()); + } + return erase(cell.mName); + } + + bool Store::erase(const std::string &id) + { + std::string key = Misc::StringUtils::lowerCase(id); + DynamicInt::iterator it = mDynamicInt.find(key); + + if (it == mDynamicInt.end()) { + return false; + } + mDynamicInt.erase(it); + mSharedInt.erase( + mSharedInt.begin() + mSharedInt.size(), + mSharedInt.end() + ); + + for (it = mDynamicInt.begin(); it != mDynamicInt.end(); ++it) { + mSharedInt.push_back(&it->second); + } + + return true; + } + + bool Store::erase(int x, int y) + { + std::pair key(x, y); + DynamicExt::iterator it = mDynamicExt.find(key); + + if (it == mDynamicExt.end()) { + return false; + } + mDynamicExt.erase(it); + mSharedExt.erase( + mSharedExt.begin() + mSharedExt.size(), + mSharedExt.end() + ); + + for (it = mDynamicExt.begin(); it != mDynamicExt.end(); ++it) { + mSharedExt.push_back(&it->second); + } + + return true; + } + + // Pathgrid + //========================================================================= + + Store::Store() + : mCells(NULL) + { + } + + void Store::setCells(Store& cells) + { + mCells = &cells; + } + RecordId Store::load(ESM::ESMReader &esm) + { + ESM::Pathgrid pathgrid; + bool isDeleted = false; + + pathgrid.load(esm, isDeleted); + + // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. + // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. + // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. + // Check whether mCell is an interior cell. This isn't perfect, will break if a Region with the same name as an interior cell is created. + // A proper fix should be made for future versions of the file format. + bool interior = mCells->search(pathgrid.mCell) != NULL; + + // Try to overwrite existing record + if (interior) + { + std::pair ret = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); + if (!ret.second) + ret.first->second = pathgrid; + } + else + { + std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), pathgrid)); + if (!ret.second) + ret.first->second = pathgrid; + } + + return RecordId("", isDeleted); + } + size_t Store::getSize() const + { + return mInt.size() + mExt.size(); + } + void Store::setUp() + { + } + const ESM::Pathgrid *Store::search(int x, int y) const + { + Exterior::const_iterator it = mExt.find(std::make_pair(x,y)); + if (it != mExt.end()) + return &(it->second); + return NULL; + } + const ESM::Pathgrid *Store::search(const std::string& name) const + { + Interior::const_iterator it = mInt.find(name); + if (it != mInt.end()) + return &(it->second); + return NULL; + } + const ESM::Pathgrid *Store::find(int x, int y) const + { + const ESM::Pathgrid* pathgrid = search(x,y); + if (!pathgrid) + { + std::ostringstream msg; + msg << "Pathgrid in cell '" << x << " " << y << "' not found"; + throw std::runtime_error(msg.str()); + } + return pathgrid; + } + const ESM::Pathgrid* Store::find(const std::string& name) const + { + const ESM::Pathgrid* pathgrid = search(name); + if (!pathgrid) + { + std::ostringstream msg; + msg << "Pathgrid in cell '" << name << "' not found"; + throw std::runtime_error(msg.str()); + } + return pathgrid; + } + const ESM::Pathgrid *Store::search(const ESM::Cell &cell) const + { + if (!(cell.mData.mFlags & ESM::Cell::Interior)) + return search(cell.mData.mX, cell.mData.mY); + else + return search(cell.mName); + } + const ESM::Pathgrid *Store::find(const ESM::Cell &cell) const + { + if (!(cell.mData.mFlags & ESM::Cell::Interior)) + return find(cell.mData.mX, cell.mData.mY); + else + return find(cell.mName); + } + + // Skill + //========================================================================= + + Store::Store() + { + } + + // Magic effect + //========================================================================= + + Store::Store() + { + } + + // Attribute + //========================================================================= + + Store::Store() + { + mStatic.reserve(ESM::Attribute::Length); + } + const ESM::Attribute *Store::search(size_t index) const + { + if (index >= mStatic.size()) { + return 0; + } + return &mStatic.at(index); + } + + const ESM::Attribute *Store::find(size_t index) const + { + const ESM::Attribute *ptr = search(index); + if (ptr == 0) { + std::ostringstream msg; + msg << "Attribute with index " << index << " not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + + void Store::setUp() + { + for (int i = 0; i < ESM::Attribute::Length; ++i) { + mStatic.push_back( + ESM::Attribute( + ESM::Attribute::sAttributeIds[i], + ESM::Attribute::sGmstAttributeIds[i], + ESM::Attribute::sGmstAttributeDescIds[i] + ) + ); + } + } + + size_t Store::getSize() const + { + return mStatic.size(); + } + + Store::iterator Store::begin() const + { + return mStatic.begin(); + } + + Store::iterator Store::end() const + { + return mStatic.end(); + } + + // Dialogue + //========================================================================= + + template<> + void Store::setUp() + { + // DialInfos marked as deleted are kept during the loading phase, so that the linked list + // structure is kept intact for inserting further INFOs. Delete them now that loading is done. + for (Static::iterator it = mStatic.begin(); it != mStatic.end(); ++it) + { + ESM::Dialogue& dial = it->second; + dial.clearDeletedInfos(); + } + + mShared.clear(); + mShared.reserve(mStatic.size()); + std::map::iterator it = mStatic.begin(); + for (; it != mStatic.end(); ++it) { + mShared.push_back(&(it->second)); + } + } + + template <> + inline RecordId Store::load(ESM::ESMReader &esm) { + // The original letter case of a dialogue ID is saved, because it's printed + ESM::Dialogue dialogue; + bool isDeleted = false; + + dialogue.loadId(esm); + + std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId); + std::map::iterator found = mStatic.find(idLower); + if (found == mStatic.end()) + { + dialogue.loadData(esm, isDeleted); + mStatic.insert(std::make_pair(idLower, dialogue)); + } + else + { + found->second.loadData(esm, isDeleted); + dialogue = found->second; + } + + return RecordId(dialogue.mId, isDeleted); + } +#if 0 + // Script + //========================================================================= + + template <> + inline RecordId Store::load(ESM::ESMReader &esm) { + ESM::Script script; + script.load(esm); + Misc::StringUtils::toLower(script.mId); + + std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); + if (inserted.second) + mShared.push_back(&inserted.first->second); + else + inserted.first->second = script; + + return RecordId(script.mId, script.mIsDeleted); + } + + // StartScript + //========================================================================= + + template <> + inline RecordId Store::load(ESM::ESMReader &esm) + { + ESM::StartScript script; + script.load(esm); + Misc::StringUtils::toLower(script.mId); + + std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); + if (inserted.second) + mShared.push_back(&inserted.first->second); + else + inserted.first->second = script; + + return RecordId(script.mId); + } +#endif } + +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 5cc66981c..b51ad9272 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -4,8 +4,6 @@ #include #include #include -#include -#include #include @@ -18,23 +16,34 @@ namespace MWWorld { - struct StoreBase + struct RecordId { + std::string mId; + bool mIsDeleted; + + RecordId(const std::string &id = "", bool isDeleted = false); + }; + + class StoreBase + { + public: virtual ~StoreBase() {} virtual void setUp() {} + + /// List identifiers of records contained in this Store (case-smashed). No-op for Stores that don't use string IDs. virtual void listIdentifier(std::vector &list) const {} virtual size_t getSize() const = 0; virtual int getDynamicSize() const { return 0; } - virtual void load(ESM::ESMReader &esm) = 0; + virtual RecordId load(ESM::ESMReader &esm) = 0; virtual bool eraseStatic(const std::string &id) {return false;} virtual void clearDynamic() {} virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - virtual void read (ESM::ESMReader& reader) {} + virtual RecordId read (ESM::ESMReader& reader) { return RecordId(); } ///< Read into dynamic storage virtual std::string getLastAddedRecordId() const { return ""; } @@ -42,6 +51,30 @@ namespace MWWorld virtual bool isLastAddedRecordDeleted() const { return false; } }; + template + class IndexedStore + { + protected: + typedef typename std::map Static; + Static mStatic; + + public: + typedef typename std::map::const_iterator iterator; + + IndexedStore(); + + iterator begin() const; + iterator end() const; + + void load(ESM::ESMReader &esm); + + int getSize() const; + void setUp(); + + const T *search(int index) const; + const T *find(int index) const; + }; + template class SharedIterator { @@ -136,216 +169,51 @@ namespace MWWorld friend class ESMStore; public: - Store() - {} - - Store(const Store &orig) - : mStatic(orig.mData) - {} + Store(); + Store(const Store &orig); typedef SharedIterator iterator; // setUp needs to be called again after - virtual void clearDynamic() - { - // remove the dynamic part of mShared - assert(mShared.size() >= mStatic.size()); - mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); - mDynamic.clear(); - } - - const T *search(const std::string &id) const { - T item; - item.mId = Misc::StringUtils::lowerCase(id); + virtual void clearDynamic(); + void setUp(); - typename Dynamic::const_iterator dit = mDynamic.find(item.mId); - if (dit != mDynamic.end()) { - return &dit->second; - } - - typename std::map::const_iterator it = mStatic.find(item.mId); - - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { - return &(it->second); - } - - return 0; - } + const T *search(const std::string &id) const; /** * Does the record with this ID come from the dynamic store? */ - bool isDynamic(const std::string &id) const { - typename Dynamic::const_iterator dit = mDynamic.find(id); - return (dit != mDynamic.end()); - } + bool isDynamic(const std::string &id) const; /** Returns a random record that starts with the named ID, or NULL if not found. */ - const T *searchRandom(const std::string &id) const - { - std::vector results; - std::for_each(mShared.begin(), mShared.end(), GetRecords(id, &results)); - if(!results.empty()) - return results[Misc::Rng::rollDice(results.size())]; - return NULL; - } + const T *searchRandom(const std::string &id) const; - const T *find(const std::string &id) const { - const T *ptr = search(id); - if (ptr == 0) { - std::ostringstream msg; - msg << T::getRecordType() << " '" << id << "' not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } + const T *find(const std::string &id) const; /** Returns a random record that starts with the named ID. An exception is thrown if none * are found. */ - const T *findRandom(const std::string &id) const - { - const T *ptr = searchRandom(id); - if(ptr == 0) - { - std::ostringstream msg; - msg << T::getRecordType() << " starting with '"< inserted = mStatic.insert(std::make_pair(record.mId, record)); - if (inserted.second) - mShared.push_back(&inserted.first->second); - - mLastAddedRecord = record; - } - - void setUp() { - } - - iterator begin() const { - return mShared.begin(); - } - - iterator end() const { - return mShared.end(); - } - - size_t getSize() const { - return mShared.size(); - } - - int getDynamicSize() const - { - return static_cast (mDynamic.size()); // truncated from unsigned __int64 if _MSC_VER && _WIN64 - } - - void listIdentifier(std::vector &list) const { - list.reserve(list.size() + getSize()); - typename std::vector::const_iterator it = mShared.begin(); - for (; it != mShared.end(); ++it) { - list.push_back((*it)->mId); - } - } - - T *insert(const T &item) { - std::string id = Misc::StringUtils::lowerCase(item.mId); - std::pair result = - mDynamic.insert(std::pair(id, item)); - T *ptr = &result.first->second; - if (result.second) { - mShared.push_back(ptr); - } else { - *ptr = item; - } - return ptr; - } - - T *insertStatic(const T &item) { - std::string id = Misc::StringUtils::lowerCase(item.mId); - std::pair result = - mStatic.insert(std::pair(id, item)); - T *ptr = &result.first->second; - if (result.second) { - mShared.push_back(ptr); - } else { - *ptr = item; - } - return ptr; - } - - - bool eraseStatic(const std::string &id) { - T item; - item.mId = Misc::StringUtils::lowerCase(id); - - typename std::map::iterator it = mStatic.find(item.mId); - - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { - // delete from the static part of mShared - typename std::vector::iterator sharedIter = mShared.begin(); - typename std::vector::iterator end = sharedIter + mStatic.size(); + const T *findRandom(const std::string &id) const; - while (sharedIter != mShared.end() && sharedIter != end) { - if((*sharedIter)->mId == item.mId) { - mShared.erase(sharedIter); - break; - } - ++sharedIter; - } - mStatic.erase(it); - } - - return true; - } - - bool erase(const std::string &id) { - std::string key = Misc::StringUtils::lowerCase(id); - typename Dynamic::iterator it = mDynamic.find(key); - if (it == mDynamic.end()) { - return false; - } - mDynamic.erase(it); + iterator begin() const; + iterator end() const; - // have to reinit the whole shared part - assert(mShared.size() >= mStatic.size()); - mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); - for (it = mDynamic.begin(); it != mDynamic.end(); ++it) { - mShared.push_back(&it->second); - } - return true; - } - - bool erase(const T &item) { - return erase(item.mId); - } + size_t getSize() const; + int getDynamicSize() const; - void write (ESM::ESMWriter& writer, Loading::Listener& progress) const - { - for (typename Dynamic::const_iterator iter (mDynamic.begin()); iter!=mDynamic.end(); - ++iter) - { - writer.startRecord (T::sRecordId); - iter->second.save (writer); - writer.endRecord (T::sRecordId); - } - } + /// @note The record identifiers are listed in the order that the records were defined by the content files. + void listIdentifier(std::vector &list) const; - void read (ESM::ESMReader& reader) - { - T record; - record.load (reader); - insert (record); + T *insert(const T &item); + T *insertStatic(const T &item); - mLastAddedRecord = record; - } + bool eraseStatic(const std::string &id); + bool erase(const std::string &id); + bool erase(const T &item); + RecordId load(ESM::ESMReader &esm); + void write(ESM::ESMWriter& writer, Loading::Listener& progress) const; + RecordId read(ESM::ESMReader& reader); +#if 0 std::string getLastAddedRecordId() const { return ESM::getRecordId(mLastAddedRecord); @@ -355,13 +223,16 @@ namespace MWWorld { return ESM::isRecordDeleted(mLastAddedRecord); } +#endif }; +#if 0 template <> inline void Store::load(ESM::ESMReader &esm) { // The original letter case of a dialogue ID is saved, because it's printed ESM::Dialogue dialogue; - dialogue.load(esm); + bool isDeleted; + dialogue.load(esm, isDeleted); std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId); std::map::iterator found = mStatic.find(idLower); @@ -371,17 +242,18 @@ namespace MWWorld } else { - found->second.mIsDeleted = dialogue.mIsDeleted; + //found->second.mIsDeleted = mIsDeleted; found->second.mType = dialogue.mType; } - + mLastAddedRecord = dialogue; } template <> inline void Store::load(ESM::ESMReader &esm) { ESM::Script script; - script.load(esm); + bool isDeleted; + script.load(esm, isDeleted); Misc::StringUtils::toLower(script.mId); std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); @@ -389,7 +261,7 @@ namespace MWWorld mShared.push_back(&inserted.first->second); else inserted.first->second = script; - + mLastAddedRecord = script; } @@ -397,7 +269,8 @@ namespace MWWorld inline void Store::load(ESM::ESMReader &esm) { ESM::StartScript script; - script.load(esm); + bool isDeleted; + script.load(esm, isDeleted); Misc::StringUtils::toLower(script.mId); std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); @@ -405,9 +278,10 @@ namespace MWWorld mShared.push_back(&inserted.first->second); else inserted.first->second = script; - + mLastAddedRecord = script; } +#endif template <> class Store : public StoreBase @@ -415,24 +289,17 @@ namespace MWWorld // For multiple ESM/ESP files we need one list per file. typedef std::vector LandTextureList; std::vector mStatic; - ESM::LandTexture mLastLoadedTexture; + //ESM::LandTexture mLastLoadedTexture; public: - Store() { - mStatic.push_back(LandTextureList()); - LandTextureList <exl = mStatic[0]; - // More than enough to hold Morrowind.esm. Extra lists for plugins will we - // added on-the-fly in a different method. - ltexl.reserve(128); - } + Store(); typedef std::vector::const_iterator iterator; // Must be threadsafe! Called from terrain background loading threads. // Not a big deal here, since ESM::LandTexture can never be modified or inserted/erased - const ESM::LandTexture *search(size_t index, size_t plugin) const { - assert(plugin < mStatic.size()); - const LandTextureList <exl = mStatic[plugin]; + const ESM::LandTexture *search(size_t index, size_t plugin) const; + const ESM::LandTexture *find(size_t index, size_t plugin) const; /// Resize the internal store to hold at least \a num plugins. void resize(size_t num); @@ -440,62 +307,22 @@ namespace MWWorld size_t getSize() const; size_t getSize(size_t plugin) const; - const ESM::LandTexture *find(size_t index, size_t plugin) const { - const ESM::LandTexture *ptr = search(index, plugin); - if (ptr == 0) { - std::ostringstream msg; - msg << "Land texture with index " << index << " not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - size_t getSize() const { - return mStatic.size(); - } - - size_t getSize(size_t plugin) const { - assert(plugin < mStatic.size()); - return mStatic[plugin].size(); - } - - void load(ESM::ESMReader &esm, size_t plugin) { - ESM::LandTexture lt; - lt.load(esm); - - // Make sure we have room for the structure - if (plugin >= mStatic.size()) { - mStatic.resize(plugin+1); - } - LandTextureList <exl = mStatic[plugin]; - if(lt.mIndex + 1 > (int)ltexl.size()) - ltexl.resize(lt.mIndex+1); - - // Store it - ltexl[lt.mIndex] = lt; - } - - void load(ESM::ESMReader &esm); - - iterator begin(size_t plugin) const { - assert(plugin < mStatic.size()); - return mStatic[plugin].begin(); - } - - iterator end(size_t plugin) const { - assert(plugin < mStatic.size()); - return mStatic[plugin].end(); - } + RecordId load(ESM::ESMReader &esm, size_t plugin); + RecordId load(ESM::ESMReader &esm); + iterator begin(size_t plugin) const; + iterator end(size_t plugin) const; +#if 0 std::string getLastAddedRecordId() const { - return ESM::getRecordId(mLastLoadedTexture); + return "";// ESM::getRecordId(mLastLoadedTexture); } bool isLastAddedRecordDeleted() const { - return ESM::isRecordDeleted(mLastLoadedTexture); + return 0;// ESM::isRecordDeleted(mLastLoadedTexture); } +#endif }; template <> @@ -503,88 +330,22 @@ namespace MWWorld { std::vector mStatic; - struct Compare - { - bool operator()(const ESM::Land *x, const ESM::Land *y) { - if (x->mX == y->mX) { - return x->mY < y->mY; - } - return x->mX < y->mX; - } - }; - public: typedef SharedIterator iterator; - virtual ~Store() - { - for (std::vector::const_iterator it = - mStatic.begin(); it != mStatic.end(); ++it) - { - delete *it; - } - - } + virtual ~Store(); - size_t getSize() const { - return mStatic.size(); - } - - iterator begin() const { - return iterator(mStatic.begin()); - } - - iterator end() const { - return iterator(mStatic.end()); - } + size_t getSize() const; + iterator begin() const; + iterator end() const; // Must be threadsafe! Called from terrain background loading threads. // Not a big deal here, since ESM::Land can never be modified or inserted/erased - ESM::Land *search(int x, int y) const { - ESM::Land land; - land.mX = x, land.mY = y; - - std::vector::const_iterator it = - std::lower_bound(mStatic.begin(), mStatic.end(), &land, Compare()); - - if (it != mStatic.end() && (*it)->mX == x && (*it)->mY == y) { - return const_cast(*it); - } - return 0; - } - - ESM::Land *find(int x, int y) const{ - ESM::Land *ptr = search(x, y); - if (ptr == 0) { - std::ostringstream msg; - msg << "Land at (" << x << ", " << y << ") not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - void load(ESM::ESMReader &esm) { - ESM::Land *ptr = new ESM::Land(); - ptr->load(esm); + ESM::Land *search(int x, int y) const; + ESM::Land *find(int x, int y) const; - // Same area defined in multiple plugins? -> last plugin wins - // Can't use search() because we aren't sorted yet - is there any other way to speed this up? - for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) - { - if ((*it)->mX == ptr->mX && (*it)->mY == ptr->mY) - { - delete *it; - mStatic.erase(it); - break; - } - } - - mStatic.push_back(ptr); - } - - void setUp() { - std::sort(mStatic.begin(), mStatic.end(), Compare()); - } + RecordId load(ESM::ESMReader &esm); + void setUp(); }; template <> @@ -618,261 +379,44 @@ namespace MWWorld DynamicInt mDynamicInt; DynamicExt mDynamicExt; - const ESM::Cell *search(const ESM::Cell &cell) const { - if (cell.isExterior()) { - return search(cell.getGridX(), cell.getGridY()); - } - return search(cell.mName); - } - + const ESM::Cell *search(const ESM::Cell &cell) const; void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); public: typedef SharedIterator iterator; - const ESM::Cell *search(const std::string &id) const { - ESM::Cell cell; - cell.mName = Misc::StringUtils::lowerCase(id); + const ESM::Cell *search(const std::string &id) const; + const ESM::Cell *search(int x, int y) const; + const ESM::Cell *searchOrCreate(int x, int y); - std::map::const_iterator it = mInt.find(cell.mName); + const ESM::Cell *find(const std::string &id) const; + const ESM::Cell *find(int x, int y) const; - if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) { - return &(it->second); - } - - DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName); - if (dit != mDynamicInt.end()) { - return &dit->second; - } - - return 0; - } - - const ESM::Cell *search(int x, int y) const { - ESM::Cell cell; - cell.mData.mX = x, cell.mData.mY = y; - - std::pair key(x, y); - DynamicExt::const_iterator it = mExt.find(key); - if (it != mExt.end()) { - return &(it->second); - } - - DynamicExt::const_iterator dit = mDynamicExt.find(key); - if (dit != mDynamicExt.end()) { - return &dit->second; - } - - return 0; - } - - const ESM::Cell *searchOrCreate(int x, int y) { - std::pair key(x, y); - DynamicExt::const_iterator it = mExt.find(key); - if (it != mExt.end()) { - return &(it->second); - } - - DynamicExt::const_iterator dit = mDynamicExt.find(key); - if (dit != mDynamicExt.end()) { - return &dit->second; - } - - ESM::Cell newCell; - newCell.mData.mX = x; - newCell.mData.mY = y; - newCell.mData.mFlags = ESM::Cell::HasWater; - newCell.mAmbi.mAmbient = 0; - newCell.mAmbi.mSunlight = 0; - newCell.mAmbi.mFog = 0; - newCell.mAmbi.mFogDensity = 0; - return &mExt.insert(std::make_pair(key, newCell)).first->second; - } - - const ESM::Cell *find(const std::string &id) const { - const ESM::Cell *ptr = search(id); - if (ptr == 0) { - std::ostringstream msg; - msg << "Interior cell '" << id << "' not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - const ESM::Cell *find(int x, int y) const { - const ESM::Cell *ptr = search(x, y); - if (ptr == 0) { - std::ostringstream msg; - msg << "Exterior at (" << x << ", " << y << ") not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - void setUp() { - typedef DynamicExt::iterator ExtIterator; - typedef std::map::iterator IntIterator; - - mSharedInt.clear(); - mSharedInt.reserve(mInt.size()); - for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) { - mSharedInt.push_back(&(it->second)); - } - - mSharedExt.clear(); - mSharedExt.reserve(mExt.size()); - for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) { - mSharedExt.push_back(&(it->second)); - } - } - - // HACK: Method implementation had to be moved to a separate cpp file, as we would otherwise get - // errors related to the compare operator used in std::find for ESM::MovedCellRefTracker::find. - // There some nasty three-way cyclic header dependency involved, which I could only fix by moving - // this method. - void load(ESM::ESMReader &esm); - - iterator intBegin() const { - return iterator(mSharedInt.begin()); - } - - iterator intEnd() const { - return iterator(mSharedInt.end()); - } + void setUp(); - iterator extBegin() const { - return iterator(mSharedExt.begin()); - } + RecordId load(ESM::ESMReader &esm); - iterator extEnd() const { - return iterator(mSharedExt.end()); - } + iterator intBegin() const; + iterator intEnd() const; + iterator extBegin() const; + iterator extEnd() const; // Return the northernmost cell in the easternmost column. - const ESM::Cell *searchExtByName(const std::string &id) const { - ESM::Cell *cell = 0; - std::vector::const_iterator it = mSharedExt.begin(); - for (; it != mSharedExt.end(); ++it) { - if (Misc::StringUtils::ciEqual((*it)->mName, id)) { - if ( cell == 0 || - ( (*it)->mData.mX > cell->mData.mX ) || - ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) - { - cell = *it; - } - } - } - return cell; - } + const ESM::Cell *searchExtByName(const std::string &id) const; // Return the northernmost cell in the easternmost column. - const ESM::Cell *searchExtByRegion(const std::string &id) const { - ESM::Cell *cell = 0; - std::vector::const_iterator it = mSharedExt.begin(); - for (; it != mSharedExt.end(); ++it) { - if (Misc::StringUtils::ciEqual((*it)->mRegion, id)) { - if ( cell == 0 || - ( (*it)->mData.mX > cell->mData.mX ) || - ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) - { - cell = *it; - } - } - } - return cell; - } - - size_t getSize() const { - return mSharedInt.size() + mSharedExt.size(); - } - - void listIdentifier(std::vector &list) const { - list.reserve(list.size() + mSharedInt.size()); - - std::vector::const_iterator it = mSharedInt.begin(); - for (; it != mSharedInt.end(); ++it) { - list.push_back((*it)->mName); - } - } - - ESM::Cell *insert(const ESM::Cell &cell) { - if (search(cell) != 0) { - std::ostringstream msg; - msg << "Failed to create "; - msg << ((cell.isExterior()) ? "exterior" : "interior"); - msg << " cell"; - - throw std::runtime_error(msg.str()); - } - ESM::Cell *ptr; - if (cell.isExterior()) { - std::pair key(cell.getGridX(), cell.getGridY()); - - // duplicate insertions are avoided by search(ESM::Cell &) - std::pair result = - mDynamicExt.insert(std::make_pair(key, cell)); - - ptr = &result.first->second; - mSharedExt.push_back(ptr); - } else { - std::string key = Misc::StringUtils::lowerCase(cell.mName); - - // duplicate insertions are avoided by search(ESM::Cell &) - std::pair result = - mDynamicInt.insert(std::make_pair(key, cell)); + const ESM::Cell *searchExtByRegion(const std::string &id) const; - ptr = &result.first->second; - mSharedInt.push_back(ptr); - } - return ptr; - } - - bool erase(const ESM::Cell &cell) { - if (cell.isExterior()) { - return erase(cell.getGridX(), cell.getGridY()); - } - return erase(cell.mName); - } - - bool erase(const std::string &id) { - std::string key = Misc::StringUtils::lowerCase(id); - DynamicInt::iterator it = mDynamicInt.find(key); - - if (it == mDynamicInt.end()) { - return false; - } - mDynamicInt.erase(it); - mSharedInt.erase( - mSharedInt.begin() + mSharedInt.size(), - mSharedInt.end() - ); - - for (it = mDynamicInt.begin(); it != mDynamicInt.end(); ++it) { - mSharedInt.push_back(&it->second); - } + size_t getSize() const; - return true; - } + void listIdentifier(std::vector &list) const; - bool erase(int x, int y) { - std::pair key(x, y); - DynamicExt::iterator it = mDynamicExt.find(key); + ESM::Cell *insert(const ESM::Cell &cell); - if (it == mDynamicExt.end()) { - return false; - } - mDynamicExt.erase(it); - mSharedExt.erase( - mSharedExt.begin() + mSharedExt.size(), - mSharedExt.end() - ); - - for (it = mDynamicExt.begin(); it != mDynamicExt.end(); ++it) { - mSharedExt.push_back(&it->second); - } + bool erase(const ESM::Cell &cell); + bool erase(const std::string &id); - return true; - } + bool erase(int x, int y); }; template <> @@ -889,165 +433,35 @@ namespace MWWorld public: - Store() - : mCells(NULL) - { - } + Store(); - void setCells(Store& cells) - { - mCells = &cells; - } - - void load(ESM::ESMReader &esm) { - ESM::Pathgrid pathgrid; - pathgrid.load(esm); - - // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. - // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. - // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. - // Check whether mCell is an interior cell. This isn't perfect, will break if a Region with the same name as an interior cell is created. - // A proper fix should be made for future versions of the file format. - bool interior = mCells->search(pathgrid.mCell) != NULL; - - // Try to overwrite existing record - if (interior) - { - std::pair ret = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); - if (!ret.second) - ret.first->second = pathgrid; - } - else - { - std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), pathgrid)); - if (!ret.second) - ret.first->second = pathgrid; - } - } - - size_t getSize() const { - return mInt.size() + mExt.size(); - } - - void setUp() { - } - - const ESM::Pathgrid *search(int x, int y) const { - Exterior::const_iterator it = mExt.find(std::make_pair(x,y)); - if (it != mExt.end()) - return &(it->second); - return NULL; - } - - const ESM::Pathgrid *search(const std::string& name) const { - Interior::const_iterator it = mInt.find(name); - if (it != mInt.end()) - return &(it->second); - return NULL; - } - - const ESM::Pathgrid *find(int x, int y) const { - const ESM::Pathgrid* pathgrid = search(x,y); - if (!pathgrid) - { - std::ostringstream msg; - msg << "Pathgrid in cell '" << x << " " << y << "' not found"; - throw std::runtime_error(msg.str()); - } - return pathgrid; - } - - const ESM::Pathgrid* find(const std::string& name) const { - const ESM::Pathgrid* pathgrid = search(name); - if (!pathgrid) - { - std::ostringstream msg; - msg << "Pathgrid in cell '" << name << "' not found"; - throw std::runtime_error(msg.str()); - } - return pathgrid; - } + void setCells(Store& cells); + RecordId load(ESM::ESMReader &esm); + size_t getSize() const; - const ESM::Pathgrid *search(const ESM::Cell &cell) const { - if (!(cell.mData.mFlags & ESM::Cell::Interior)) - return search(cell.mData.mX, cell.mData.mY); - else - return search(cell.mName); - } + void setUp(); - const ESM::Pathgrid *find(const ESM::Cell &cell) const { - if (!(cell.mData.mFlags & ESM::Cell::Interior)) - return find(cell.mData.mX, cell.mData.mY); - else - return find(cell.mName); - } + const ESM::Pathgrid *search(int x, int y) const; + const ESM::Pathgrid *search(const std::string& name) const; + const ESM::Pathgrid *find(int x, int y) const; + const ESM::Pathgrid* find(const std::string& name) const; + const ESM::Pathgrid *search(const ESM::Cell &cell) const; + const ESM::Pathgrid *find(const ESM::Cell &cell) const; }; - template - class IndexedStore - { - protected: - typedef typename std::map Static; - Static mStatic; - - public: - typedef typename std::map::const_iterator iterator; - - IndexedStore() {} - - iterator begin() const { - return mStatic.begin(); - } - - iterator end() const { - return mStatic.end(); - } - - void load(ESM::ESMReader &esm) { - T record; - record.load(esm); - - // Try to overwrite existing record - std::pair ret = mStatic.insert(std::make_pair(record.mIndex, record)); - if (!ret.second) - ret.first->second = record; - } - - int getSize() const { - return mStatic.size(); - } - - void setUp() { - } - - const T *search(int index) const { - typename Static::const_iterator it = mStatic.find(index); - if (it != mStatic.end()) - return &(it->second); - return NULL; - } - - const T *find(int index) const { - const T *ptr = search(index); - if (ptr == 0) { - std::ostringstream msg; - msg << T::getRecordType() << " with index " << index << " not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - }; template <> - struct Store : public IndexedStore + class Store : public IndexedStore { - Store() {} + public: + Store(); }; template <> - struct Store : public IndexedStore + class Store : public IndexedStore { - Store() {} + public: + Store(); }; template <> @@ -1058,52 +472,19 @@ namespace MWWorld public: typedef std::vector::const_iterator iterator; - Store() { - mStatic.reserve(ESM::Attribute::Length); - } + Store(); - const ESM::Attribute *search(size_t index) const { - if (index >= mStatic.size()) { - return 0; - } - return &mStatic.at(index); - } + const ESM::Attribute *search(size_t index) const; + const ESM::Attribute *find(size_t index) const; - const ESM::Attribute *find(size_t index) const { - const ESM::Attribute *ptr = search(index); - if (ptr == 0) { - std::ostringstream msg; - msg << "Attribute with index " << index << " not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } + void setUp(); - void setUp() { - for (int i = 0; i < ESM::Attribute::Length; ++i) { - mStatic.push_back( - ESM::Attribute( - ESM::Attribute::sAttributeIds[i], - ESM::Attribute::sGmstAttributeIds[i], - ESM::Attribute::sGmstAttributeDescIds[i] - ) - ); - } - } - - size_t getSize() const { - return mStatic.size(); - } - - iterator begin() const { - return mStatic.begin(); - } - - iterator end() const { - return mStatic.end(); - } + size_t getSize() const; + iterator begin() const; + iterator end() const; }; +#if 0 template<> inline void Store::setUp() { @@ -1122,6 +503,7 @@ namespace MWWorld mShared.push_back(&(it->second)); } } +#endif } //end namespace diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index eb7dd901d..48c8be610 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -243,11 +243,14 @@ namespace Gui std::string textureName = name; Ogre::Image image; image.loadDynamicImage(&textureData[0], width, height, Ogre::PF_BYTE_RGBA); - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual(textureName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - width, height, 0, Ogre::PF_BYTE_RGBA); - texture->loadImage(image); + if (!Ogre::TextureManager::getSingleton().resourceExists(textureName)) + { + Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual(textureName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + width, height, 0, Ogre::PF_BYTE_RGBA); + texture->loadImage(image); + } if (exportToFile) image.save(resourceName + ".png"); diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index f54018a87..90721cd3c 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -73,7 +73,6 @@ public: static void lowerCaseInPlace(std::string &inout) { for (unsigned int i=0; i 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) kfname.replace(kfname.size()-4, 4, ".kf"); if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(kfname)) diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 0009c9f83..d6b261cd7 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -711,7 +711,7 @@ private: std::string fullname = name+"@index="+Ogre::StringConverter::toString(shape->recIndex); if(shape->name.length() > 0) fullname += "@shape="+shape->name; - Misc::StringUtils::toLower(fullname); + Misc::StringUtils::lowerCaseInPlace(fullname); Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); if(meshMgr.getByName(fullname).isNull()) @@ -944,7 +944,7 @@ private: std::string fullname = name+"@index="+Ogre::StringConverter::toString(partnode->recIndex); if(partnode->name.length() > 0) fullname += "@type="+partnode->name; - Misc::StringUtils::toLower(fullname); + Misc::StringUtils::lowerCaseInPlace(fullname); Ogre::ParticleSystem *partsys = sceneNode->getCreator()->createParticleSystem(); @@ -1172,7 +1172,7 @@ private: nextpos = std::distance(str.begin(), ++last); } std::string result = str.substr(pos, nextpos-pos); - textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::toLower(result))); + textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::lowerCase(result))); pos = nextpos; } @@ -1377,7 +1377,7 @@ ObjectScenePtr Loader::createObjects(Ogre::SceneNode *parentNode, std::string na { ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); - Misc::StringUtils::toLower(name); + Misc::StringUtils::lowerCaseInPlace(name); NIFObjectLoader::load(parentNode, scene, name, group); for(size_t i = 0;i < scene->mEntities.size();i++) @@ -1399,7 +1399,7 @@ ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bo { ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); - Misc::StringUtils::toLower(name); + Misc::StringUtils::lowerCaseInPlace(name); NIFObjectLoader::load(parentNode, scene, name, group); bool isskinned = false; @@ -1422,8 +1422,8 @@ ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bo // accepts anything named "filter*" or "tri filter*" std::string filter = "@shape=tri "+bonefilter; std::string filter2 = "@shape="+bonefilter; - Misc::StringUtils::toLower(filter); - Misc::StringUtils::toLower(filter2); + Misc::StringUtils::lowerCaseInPlace(filter); + Misc::StringUtils::lowerCaseInPlace(filter2); for(size_t i = 0;i < scene->mEntities.size();i++) { Ogre::Entity *entity = scene->mEntities[i]; @@ -1465,7 +1465,7 @@ ObjectScenePtr Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string { ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); - Misc::StringUtils::toLower(name); + Misc::StringUtils::lowerCaseInPlace(name); NIFObjectLoader::load(parentNode, scene, name, group, 0xC0000000); if(scene->mSkelBase) diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp index 936bfb435..e68221d1d 100644 --- a/components/nifogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include /* FIXME: "Nif" isn't really an appropriate emitter name. */ class NifEmitter : public Ogre::ParticleEmitter @@ -173,7 +173,7 @@ public: Ogre::Real& timeToLive = particle->timeToLive; #endif - Ogre::Node* emitterBone = mEmitterBones.at(OEngine::Misc::Rng::rollDice(mEmitterBones.size())); + Ogre::Node* emitterBone = mEmitterBones.at(Misc::Rng::rollDice(mEmitterBones.size())); position = xOff + yOff + zOff + mParticleBone->_getDerivedOrientation().Inverse() * (emitterBone->_getDerivedPosition() diff --git a/libs/openengine/CMakeLists.txt b/libs/openengine/CMakeLists.txt index e9a1bcd9e..7a0a791d1 100644 --- a/libs/openengine/CMakeLists.txt +++ b/libs/openengine/CMakeLists.txt @@ -23,7 +23,7 @@ set(OENGINE_BULLET bullet/trace.h ) -set(OENGINE_ALL ${OENGINE_GUI} ${OENGINE_BULLET}) +set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_BULLET}) set(OENGINE_LIBRARY "oengine") set(OENGINE_LIBRARY ${OENGINE_LIBRARY} PARENT_SCOPE) diff --git a/libs/openengine/ogre/selectionbuffer.cpp b/libs/openengine/ogre/selectionbuffer.cpp index 741b672ff..dbdc6c8b2 100644 --- a/libs/openengine/ogre/selectionbuffer.cpp +++ b/libs/openengine/ogre/selectionbuffer.cpp @@ -10,7 +10,7 @@ #include -#include +#include #include @@ -147,7 +147,7 @@ namespace Render void SelectionBuffer::getNextColour () { - Ogre::ARGB color = static_cast(OEngine::Misc::Rng::rollClosedProbability() * std::numeric_limits::max()); + Ogre::ARGB color = static_cast(Misc::Rng::rollClosedProbability() * std::numeric_limits::max()); if (mCurrentColour.getAsARGB () == color) { From 3982573035b726dd8a1ffbe261bc342f79c8aecb Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 6 Oct 2018 13:35:51 +1000 Subject: [PATCH 359/441] BSA enhancements. * Implement hash based lookup for TES3 BSA files. * Added TES4/TES5 BSA support. * Implemented a hack (non-portable code) in an attempt to reduce startup time under Windows because Boost::filesystem seems to take forever on GetFileAttributeW. This implementation uses FindFirstFile/FindNextFile/FindClose instead. --- CMakeLists.txt | 7 +- apps/opencs/CMakeLists.txt | 2 + apps/opencs/editor.cpp | 17 + apps/opencs/editor.hpp | 1 + apps/openmw/CMakeLists.txt | 2 + apps/openmw/engine.cpp | 6 + apps/openmw/engine.hpp | 2 + apps/openmw/main.cpp | 9 + components/CMakeLists.txt | 2 +- components/bsa/bsa_archive.cpp | 195 ++++++++++- components/bsa/bsa_archive.hpp | 1 + components/bsa/bsa_file.cpp | 69 +++- components/bsa/bsa_file.hpp | 2 + components/bsa/resources.cpp | 9 +- components/bsa/resources.hpp | 2 +- components/bsa/tes4bsa_file.cpp | 339 +++++++++++++++++++ components/bsa/tes4bsa_file.hpp | 103 ++++++ extern/BSAOpt/CMakeLists.txt | 17 + extern/BSAOpt/hash.cpp | 108 ++++++ extern/BSAOpt/hash.hpp | 42 +++ plugins/mygui_resource_plugin/CMakeLists.txt | 2 + 21 files changed, 917 insertions(+), 20 deletions(-) create mode 100644 components/bsa/tes4bsa_file.cpp create mode 100644 components/bsa/tes4bsa_file.hpp create mode 100644 extern/BSAOpt/CMakeLists.txt create mode 100644 extern/BSAOpt/hash.cpp create mode 100644 extern/BSAOpt/hash.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ff5191c46..2ec616cbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,9 @@ if(USE_SYSTEM_TINYXML) endif() endif() +# TES4 +find_package(ZLIB REQUIRED) + # Platform specific if (WIN32) if(NOT MINGW) @@ -257,9 +260,10 @@ include_directories("." ${LIBS_DIR} ${MYGUI_PLATFORM_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} ${BULLET_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIRS} ) -link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${OGRE_LIB_DIR} ${MYGUI_LIB_DIR}) +link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${OGRE_LIB_DIR} ${MYGUI_LIB_DIR} ${ZLIB_LIB_DIR}) if(MYGUI_STATIC) add_definitions(-DMYGUI_STATIC) @@ -577,6 +581,7 @@ add_subdirectory (extern/ogre-ffmpeg-videoplayer) add_subdirectory (extern/oics) add_subdirectory (extern/sdl4ogre) add_subdirectory (extern/murmurhash) +add_subdirectory (extern/BSAOpt) # Components add_subdirectory (components) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 7f3709b8a..ec686c4e2 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -211,7 +211,9 @@ target_link_libraries(openmw-cs ${OGRE_Overlay_LIBRARIES} ${OGRE_STATIC_PLUGINS} ${SHINY_LIBRARIES} + ${ZLIB_LIBRARY} ${MURMURHASH_LIBRARIES} + ${BSAOPTHASH_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 79f32619e..cd5f6753d 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -30,6 +30,7 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) { std::pair > config = readConfig(); + std::vector tes4config = readTES4Config(); setupDataFiles (config.first); @@ -44,6 +45,9 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) Bsa::registerResources (Files::Collections (config.first, !mFsStrict), config.second, true, mFsStrict); + // useLooseFiles is set false, since it is already done above + Bsa::registerResources (Files::Collections (config.first, !mFsStrict), tes4config, /*useLooseFiles*/false, + mFsStrict, /*isTes4*/true); mDocumentManager.listResources(); @@ -182,6 +186,19 @@ std::pair > CS::Editor::readConfi return std::make_pair (canonicalPaths, variables["fallback-archive"].as >()); } +std::vector CS::Editor::readTES4Config() +{ + boost::program_options::variables_map variables; + boost::program_options::options_description desc("Syntax: openmw-cs \nAllowed options"); + + desc.add_options() + ("fallback-tes4archive", boost::program_options::value >()-> + default_value(std::vector(), "fallback-tes4archive")->multitoken()); + + mCfgMgr.readConfiguration(variables, desc, /*quiet*/true); + return variables["fallback-tes4archive"].as >(); +} + void CS::Editor::createGame() { mStartup.hide(); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 33f5fd3b3..5eae1258a 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -73,6 +73,7 @@ namespace CS void setupDataFiles (const Files::PathContainer& dataDirs); std::pair > readConfig(bool quiet=false); + std::vector readTES4Config(); ///< \return data paths // not implemented diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index ffa52042c..cdb1691f0 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -117,6 +117,8 @@ target_link_libraries(openmw ${OGRE_LIBRARIES} ${OGRE_STATIC_PLUGINS} ${SHINY_LIBRARIES} + ${BSAOPTHASH_LIBRARIES} + ${ZLIB_LIBRARY} ${OPENAL_LIBRARY} ${SOUND_INPUT_LIBRARY} ${BULLET_LIBRARIES} diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 539539db9..1bc3b48c5 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -252,6 +252,10 @@ void OMW::Engine::addArchive (const std::string& archive) { mArchives.push_back(archive); } +void OMW::Engine::addTES4Archive (const std::string& archive) { + mTES4Archives.push_back(archive); +} + // Set resource dir void OMW::Engine::setResourceDir (const boost::filesystem::path& parResDir) { @@ -365,6 +369,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mOgre->createWindow("OpenMW", windowSettings); Bsa::registerResources (mFileCollections, mArchives, true, mFSStrict); + // useLooseFiles is set false, since it is already done above + Bsa::registerResources (mFileCollections, mTES4Archives, /*useLooseFiles*/false, mFSStrict, /*isTes4*/true); // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 3b088595c..79b047cd1 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -67,6 +67,7 @@ namespace OMW ToUTF8::Utf8Encoder* mEncoder; Files::PathContainer mDataDirs; std::vector mArchives; + std::vector mTES4Archives; boost::filesystem::path mResDir; OEngine::Render::OgreRenderer *mOgre; std::string mCellName; @@ -137,6 +138,7 @@ namespace OMW /// Add BSA archive void addArchive(const std::string& archive); + void addTES4Archive(const std::string& archive); /// Set resource dir void setResourceDir(const boost::filesystem::path& parResDir); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 2d2c9af0c..c36fcc5e9 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -112,6 +112,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") ->multitoken(), "set fallback BSA archives (later archives have higher priority)") + ("fallback-tes4archive", bpo::value()->default_value(StringsVector(), "fallback-tes4archive") + ->multitoken(), "set fallback TES4 BSA archives (later archives have higher priority)") + ("resources", bpo::value()->default_value("resources"), "set resources directory") @@ -240,6 +243,12 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.addArchive(*it); } + StringsVector tes4archives = variables["fallback-tes4archive"].as(); + for (StringsVector::const_iterator it = tes4archives.begin(); it != tes4archives.end(); ++it) + { + engine.addTES4Archive(*it); + } + engine.setResourceDir(variables["resources"].as()); StringsVector content = variables["content"].as(); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index da0428ec2..9b6f9fef9 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -31,7 +31,7 @@ add_component_dir (nifoverrides ) add_component_dir (bsa - bsa_archive bsa_file resources + bsa_archive bsa_file resources tes4bsa_file ) add_component_dir (nif diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 12836ea70..114abee20 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -23,7 +23,10 @@ #include "bsa_archive.hpp" +#include + #include +#include #include #include @@ -41,10 +44,92 @@ #define OGRE_CONST #endif +#include #include "bsa_file.hpp" +#include "tes4bsa_file.hpp" #include "../files/constrainedfiledatastream.hpp" +namespace +{ +// Concepts from answer by Remy Lebeau +// https://stackoverflow.com/questions/15068475/recursive-hard-disk-search-with-findfirstfile-findnextfile-c +// +// Also see https://msdn.microsoft.com/en-us/library/aa365200%28VS.85%29.aspx +// +// From 34.5 sec down to 18.5 sec on laptop with many of the data files on an external USB drive +#if defined _WIN32 || defined _WIN64 + +#include +#include // auto_ptr + + // FIXME: not tested unicode path and filenames + DWORD indexFiles(const std::string& rootDir, const std::string& subdir, + std::map& files, std::map& index) + { + HANDLE hFind = INVALID_HANDLE_VALUE; + WIN32_FIND_DATA ffd; + std::string path = rootDir + ((subdir == "") ? "" : "\\" +subdir); + + hFind = FindFirstFile((path + "\\*").c_str(), &ffd); + if (INVALID_HANDLE_VALUE == hFind) + return ERROR_INVALID_HANDLE; + + std::auto_ptr > subDirs; + std::string filename; + + do + { + filename = std::string(ffd.cFileName); + + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (filename != "." && filename != "..") + { + if (subDirs.get() == nullptr) + subDirs.reset(new std::vector); + + subDirs->push_back(filename); + } + } + else + { + std::uint64_t folderHash = GenOBHash(subdir, filename); + + std::map::iterator iter = files.find(folderHash); + if (iter != files.end()) + throw std::runtime_error ("duplicate hash found"); + + files[folderHash] = path + "\\" + filename; + + std::string entry = ((subdir == "") ? "" : subdir + "\\") + filename; + std::replace(entry.begin(), entry.end(), '\\', '/'); + index.insert(std::make_pair (entry, path + "/" + filename)); + } + } while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); + + DWORD dwError = GetLastError(); + if (dwError != ERROR_NO_MORE_FILES) + return dwError; + + if (subDirs.get() != nullptr) + { + for (size_t i = 0; i < subDirs->size(); ++i) + { + std::string dir = subDirs->at(i); + boost::algorithm::to_lower(dir); + // FIXME: ignoring errors for now + dwError = indexFiles(rootDir, ((subdir == "") ? "" : subdir + "\\") + dir, files, index); + } + } + + return 0; + } +#endif +} + using namespace Ogre; static bool fsstrict = false; @@ -78,6 +163,8 @@ class DirArchive: public Ogre::Archive index mIndex; + std::map mFiles; + index::const_iterator lookup_filename (std::string const & filename) const { std::string normalized = normalize_path (filename.begin (), filename.end ()); @@ -89,6 +176,10 @@ public: DirArchive(const String& name) : Archive(name, "Dir") { +#if defined _WIN32 || defined _WIN64 + indexFiles(name, "", mFiles, mIndex); +#else + typedef boost::filesystem::recursive_directory_iterator directory_iterator; directory_iterator end; @@ -109,6 +200,7 @@ public: mIndex.insert (std::make_pair (searchable, proper)); } +#endif } bool isCaseSensitive() const { return fsstrict; } @@ -119,6 +211,27 @@ public: virtual DataStreamPtr open(const String& filename, bool readonly = true) OGRE_CONST { +#if defined _WIN32 || defined _WIN64 + boost::filesystem::path p(filename); + std::string file = p.filename().string(); + + p.remove_filename(); + std::string dir = p.string(); + boost::algorithm::to_lower(dir); + std::replace(dir.begin(), dir.end(), '/', '\\'); + + std::uint64_t hash = GenOBHash(dir, file); + + std::map::const_iterator it = mFiles.find(hash); + if (it == mFiles.end()) + { + std::ostringstream os; + os << "The file '" << filename << "' could not be found."; + throw std::runtime_error (os.str()); + } + + return openConstrainedFileDataStream (it->second.c_str()); +#else index::const_iterator i = lookup_filename (filename); if (i == mIndex.end ()) @@ -129,6 +242,7 @@ public: } return openConstrainedFileDataStream (i->second.c_str ()); +#endif } StringVectorPtr list(bool recursive = true, bool dirs = false) @@ -157,7 +271,25 @@ public: bool exists(const String& filename) { +#if defined _WIN32 || defined _WIN64 + boost::filesystem::path p(filename); + std::string file = p.filename().string(); + + p.remove_filename(); + std::string dir = p.string(); + boost::algorithm::to_lower(dir); + std::replace(dir.begin(), dir.end(), '/', '\\'); + + std::uint64_t hash = GenOBHash(dir, file); + + std::map::const_iterator it = mFiles.find(hash); + if (it == mFiles.end()) + return false; + + return true; +#else return lookup_filename(filename) != mIndex.end (); +#endif } time_t getModifiedTime(const String&) { return 0; } @@ -223,6 +355,8 @@ public: : Archive(name, "BSA") { arc.open(name); } + BSAArchive(const String& name, const std::string& type) : Archive(name, type) {} + bool isCaseSensitive() const { return false; } // The archive is loaded in the constructor, and never unloaded. @@ -241,7 +375,7 @@ public: return narc->getFile(filename.c_str()); } - bool exists(const String& filename) { + virtual bool exists(const String& filename) { return arc.exists(filename.c_str()); } @@ -259,7 +393,7 @@ public: return findFileInfo ("*", recursive, dirs); } - StringVectorPtr find(const String& pattern, bool recursive = true, + virtual StringVectorPtr find(const String& pattern, bool recursive = true, bool dirs = false) { std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); @@ -306,6 +440,24 @@ public: } }; +class TES4BSAArchive : public BSAArchive +{ + Bsa::TES4BSAFile arc; + +public: + TES4BSAArchive::TES4BSAArchive(const String& name) : BSAArchive(name, "TES4BSA") { arc.open(name); } + + virtual DataStreamPtr open(const String& filename, bool readonly = true) + { + return arc.getFile(filename); + } + + virtual bool exists(const String& filename) + { + return arc.exists(filename); + } +}; + // An archive factory for BSA archives class BSAArchiveFactory : public ArchiveFactory { @@ -351,9 +503,32 @@ public: void destroyInstance( Archive* arch) { delete arch; } }; +class TES4BSAArchiveFactory : public ArchiveFactory +{ +public: + const String& getType() const + { + static String name = "TES4BSA"; + return name; + } + + Archive *createInstance( const String& name ) + { + return new TES4BSAArchive(name); + } + + virtual Archive* createInstance(const String& name, bool readOnly) + { + return new TES4BSAArchive(name); + } + + void destroyInstance( Archive* arch) { delete arch; } +}; + static bool init = false; static bool init2 = false; +static bool init3 = false; static void insertBSAFactory() { @@ -364,6 +539,15 @@ static void insertBSAFactory() } } +static void insertTES4BSAFactory() +{ + if(!init3) + { + ArchiveManager::getSingleton().addArchiveFactory( new TES4BSAArchiveFactory ); + init3 = true; + } +} + static void insertDirFactory() { if(!init2) @@ -386,6 +570,13 @@ void addBSA(const std::string& name, const std::string& group) addResourceLocation(name, "BSA", group, true); } +void addTES4BSA(const std::string& name, const std::string& group) +{ + insertTES4BSAFactory(); + ResourceGroupManager::getSingleton(). + addResourceLocation(name, "TES4BSA", group, true); +} + void addDir(const std::string& name, const bool& fs, const std::string& group) { fsstrict = fs; diff --git a/components/bsa/bsa_archive.hpp b/components/bsa/bsa_archive.hpp index 7f9ebaae1..d930c2571 100644 --- a/components/bsa/bsa_archive.hpp +++ b/components/bsa/bsa_archive.hpp @@ -33,6 +33,7 @@ namespace Bsa /// Add the given BSA file as an input archive in the Ogre resource /// system. void addBSA(const std::string& file, const std::string& group="General"); +void addTES4BSA(const std::string& file, const std::string& group="General"); void addDir(const std::string& file, const bool& fs, const std::string& group="General"); } diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 3bf73ede2..40bfce2ac 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -27,9 +27,41 @@ #include #include +#include #include "../files/constrainedfiledatastream.hpp" +namespace +{ + // see: http://en.uesp.net/wiki/Tes3Mod:BSA_File_Format + std::uint64_t getHash(const char *name) + { + unsigned int len = (unsigned int)strlen(name); + std::uint64_t hash; + + unsigned l = (len>>1); + unsigned sum, off, temp, i, n, hash1; + + for(sum = off = i = 0; i < l; i++) { + sum ^= (((unsigned)(name[i]))<<(off&0x1F)); + off += 8; + } + hash1 = sum; + + for(sum = off = 0; i < len; i++) { + temp = (((unsigned)(name[i]))<<(off&0x1F)); + sum ^= temp; + n = temp & 0x1F; + sum = (sum << (32-n)) | (sum >> n); // binary "rotate right" + off += 8; + } + hash = sum; + hash <<= 32; + hash += hash1; + return hash; + } +} + using namespace std; using namespace Bsa; @@ -143,7 +175,14 @@ void BSAFile::readHeader() fail("Archive contains offsets outside itself"); // Add the file name to the lookup - lookup[fs.name] = i; + //lookup[fs.name] = i; + } + + std::uint64_t hash; + for (size_t i = 0; i < filenum; ++i) + { + input.read(reinterpret_cast(&hash), 8); + mFiles[hash] = files[i]; } isLoaded = true; @@ -152,13 +191,14 @@ void BSAFile::readHeader() /// Get the index of a given file name, or -1 if not found int BSAFile::getIndex(const char *str) const { - Lookup::const_iterator it = lookup.find(str); - if(it == lookup.end()) + std::string name(str); + boost::algorithm::to_lower(name); + std::uint64_t hash = getHash(name.c_str()); + std::map::const_iterator iter = mFiles.find(hash); + if (iter != mFiles.end()) + return 0; // NOTE: this is a bit of a hack, exists() only checks for '-1' + else return -1; - - int res = it->second; - assert(res >= 0 && (size_t)res < files.size()); - return res; } /// Open an archive file. @@ -171,10 +211,15 @@ void BSAFile::open(const string &file) Ogre::DataStreamPtr BSAFile::getFile(const char *file) { assert(file); - int i = getIndex(file); - if(i == -1) - fail("File not found: " + string(file)); - const FileStruct &fs = files[i]; - return openConstrainedFileDataStream (filename.c_str (), fs.offset, fs.fileSize); + std::string name(file); + boost::algorithm::to_lower(name); + std::uint64_t hash = getHash(name.c_str()); + std::map::const_iterator it = mFiles.find(hash); + if (it != mFiles.end()) + { + const FileStruct &fs = it->second; + return openConstrainedFileDataStream (filename.c_str (), fs.offset, fs.fileSize); + } + fail("File not found: " + string(file)); } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 017adf1e3..5379d20e3 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -82,6 +82,8 @@ private: typedef std::map Lookup; Lookup lookup; + std::map mFiles; + /// Error handling void fail(const std::string &msg); diff --git a/components/bsa/resources.cpp b/components/bsa/resources.cpp index b66da1a76..c5075ec69 100644 --- a/components/bsa/resources.cpp +++ b/components/bsa/resources.cpp @@ -9,7 +9,7 @@ #include "bsa_archive.hpp" void Bsa::registerResources (const Files::Collections& collections, - const std::vector& archives, bool useLooseFiles, bool fsStrict) + const std::vector& archives, bool useLooseFiles, bool fsStrict, bool isTes4) { const Files::PathContainer& dataDirs = collections.getPaths(); @@ -34,13 +34,16 @@ void Bsa::registerResources (const Files::Collections& collections, if (collections.doesExist(*archive)) { // Last BSA has the highest priority - std::string groupName = "DataBSA" + Ogre::StringConverter::toString(archives.size()-i, 8, '0'); + std::string groupName = (isTes4 ? "TES4BSA" : "DataBSA") + Ogre::StringConverter::toString(archives.size()-i, 8, '0'); Ogre::ResourceGroupManager::getSingleton ().createResourceGroup (groupName); const std::string archivePath = collections.getPath(*archive).string(); std::cout << "Adding BSA archive " << archivePath << std::endl; - Bsa::addBSA(archivePath, groupName); + if (!isTes4) + Bsa::addBSA(archivePath, groupName); + else + Bsa::addTES4BSA(archivePath, groupName); ++i; } else diff --git a/components/bsa/resources.hpp b/components/bsa/resources.hpp index 8c3fb7bef..b0723b3eb 100644 --- a/components/bsa/resources.hpp +++ b/components/bsa/resources.hpp @@ -9,7 +9,7 @@ namespace Bsa { void registerResources (const Files::Collections& collections, - const std::vector& archives, bool useLooseFiles, bool fsStrict); + const std::vector& archives, bool useLooseFiles, bool fsStrict, bool isTes4=false); ///< Register resources directories and archives as OGRE resources groups } diff --git a/components/bsa/tes4bsa_file.cpp b/components/bsa/tes4bsa_file.cpp new file mode 100644 index 000000000..33caefd0b --- /dev/null +++ b/components/bsa/tes4bsa_file.cpp @@ -0,0 +1,339 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (bsa_file.cpp) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + TES4 stuff added by cc9cii 2018 + + */ +#include "tes4bsa_file.hpp" + +#include +#include + +#include +#include +#include +#include + +#include + +#include // see: http://en.uesp.net/wiki/Tes4Mod:Hash_Calculation + +#undef TEST_UNIQUE_HASH + +namespace +{ + void getBZString(std::string& str, boost::filesystem::ifstream& filestream) + { + char size = 0; + filestream.read(&size, 1); + + boost::scoped_array buf(new char[size]); + filestream.read(buf.get(), size); + + if (buf[size - 1] != 0) + str.assign(buf.get(), size); + else + str.assign(buf.get(), size - 1); // don't copy null terminator + assert((size_t)size-1 == str.size() && "getBZString string size mismatch"); + return; + } +} + +using namespace Bsa; + + +/// Error handling +void TES4BSAFile::fail(const std::string& msg) +{ + throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename); +} + +/// Read header information from the input source +void TES4BSAFile::readHeader() +{ + assert(!isLoaded); + + namespace bfs = boost::filesystem; + bfs::ifstream input(bfs::path(mFilename), std::ios_base::binary); + + // Total archive size + std::streamoff fsize = 0; + if(input.seekg(0, std::ios_base::end)) + { + fsize = input.tellg(); + input.seekg(0); + } + + if(fsize < 36) // header is 36 bytes + fail("File too small to be a valid BSA archive"); + + // Get essential header numbers + //size_t dirsize, filenum; + std::uint32_t archiveFlags, folderCount, fileCount, totalFileNameLength; + { + // First 36 bytes + std::uint32_t header[9]; + + input.read(reinterpret_cast(header), 36); + + if(header[0] != 0x00415342 /*"BSA\x00"*/ || (header[1] != 0x67 /*TES4*/ && header[1] != 0x68 /*TES5*/)) + fail("Unrecognized TES4 BSA header"); + + // header[2] is offset, should be 36 = 0x24 which is the size of the header + + // Oblivion - Meshes.bsa + // + // 0111 1000 0111 = 0x0787 + // ^^^ ^ ^^^ + // ||| | ||+-- has names for dirs (mandatory?) + // ||| | |+--- has names for files (mandatory?) + // ||| | +---- files are compressed by default + // ||| | + // ||| +---------- unknown (TES5: retain strings during startup) + // ||+------------ unknown (TES5: embedded file names) + // |+------------- unknown + // +-------------- unknown + // + archiveFlags = header[3]; + folderCount = header[4]; + fileCount = header[5]; + //totalFolderNameLength = header[6]; + totalFileNameLength = header[7]; + //fileFlags = header[8]; // FIXME: an opportunity to optimize here + + mCompressedByDefault = (archiveFlags & 0x4) != 0; + mEmbeddedFileNames = header[1] == 0x68 /*TES5*/ && (archiveFlags & 0x100) != 0; + } + + // TODO: more checks for BSA file corruption + + // folder records + std::uint64_t hash; + FolderRecord fr; + for (std::uint32_t i = 0; i < folderCount; ++i) + { + input.read(reinterpret_cast(&hash), 8); + input.read(reinterpret_cast(&fr.count), 4); // not sure purpose of count + input.read(reinterpret_cast(&fr.offset), 4); // not sure purpose of offset + + std::map::const_iterator lb = mFolders.lower_bound(hash); + if (lb != mFolders.end() && !(mFolders.key_comp()(hash, lb->first))) + fail("Archive found duplicate folder name hash"); + else + mFolders.insert(lb, std::pair(hash, fr)); + } + + // file record blocks + std::uint64_t fileHash; + FileRecord file; + + std::string folder(""); + std::uint64_t folderHash; + if ((archiveFlags & 0x1) == 0) + folderCount = 1; // TODO: not tested + + for (std::uint32_t i = 0; i < folderCount; ++i) + { + if ((archiveFlags & 0x1) != 0) + getBZString(folder, input); + + folderHash = GenOBHash(folder, std::string("")); + + std::map::iterator iter = mFolders.find(folderHash); + if (iter == mFolders.end()) + fail("Archive folder name hash not found"); + + for (std::uint32_t j = 0; j < iter->second.count; ++j) + { + input.read(reinterpret_cast(&fileHash), 8); + input.read(reinterpret_cast(&file.size), 4); + input.read(reinterpret_cast(&file.offset), 4); + + std::map::const_iterator lb = iter->second.files.lower_bound(fileHash); + if (lb != iter->second.files.end() && !(iter->second.files.key_comp()(fileHash, lb->first))) + fail("Archive found duplicate file name hash"); + + iter->second.files.insert(lb, std::pair(fileHash, file)); + } + } + + // file record blocks + if ((archiveFlags & 0x2) != 0) + { + mStringBuf.resize(totalFileNameLength); + input.read(&mStringBuf[0], mStringBuf.size()); // TODO: maybe useful in building a lookup map? + } + + // TODO: more checks for BSA file corruption + + isLoaded = true; +} + +TES4BSAFile::FileRecord TES4BSAFile::getFileRecord(const std::string& str) const +{ + boost::filesystem::path p(str); + std::string stem = p.stem().string(); + std::string ext = p.extension().string(); + std::string filename = p.filename().string(); + p.remove_filename(); + + std::string folder = p.string(); + // GenOBHash already converts to lowercase and replaces file separators but not for path + boost::algorithm::to_lower(folder); + std::replace(folder.begin(), folder.end(), '/', '\\'); + + std::uint64_t folderHash = GenOBHash(folder, std::string("")); + + std::map::const_iterator it = mFolders.find(folderHash); + if (it == mFolders.end()) + return FileRecord(); // folder not found, return default which has offset of -1 + + boost::algorithm::to_lower(stem); + boost::algorithm::to_lower(ext); + std::uint64_t fileHash = GenOBHashPair(stem, ext); + std::map::const_iterator iter = it->second.files.find(fileHash); + if (iter == it->second.files.end()) + return FileRecord(); // file not found, return default which has offset of -1 + + // cache for next time + std::uint64_t hash = GenOBHash(folder, filename); + +#if defined (TEST_UNIQUE_HASH) + FileList::const_iterator lb = mFiles.lower_bound(hash); + if (lb != mFiles.end() && !(mFiles.key_comp()(hash, lb->first))) + { + // found, check if same filename + if (lb->second.fileName == str) + return iter->second; // same name, should not have got here!! + else + { + // different filename, hash is not unique! + std::cerr << "BSA hash collision: " << str << std::hex << "0x" << hash << std::endl; + + return iter->second; // return without cashing + } + } + + // not found, cache for later + const_cast(mFiles).insert(lb, std::pair(hash, iter->second)); + const_cast(mFiles)[hash].fileName = str; +#else + const_cast(mFiles)[hash] = iter->second; // NOTE: const hack +#endif + return iter->second; +} + +bool TES4BSAFile::exists(const std::string& str) const +{ + // check cache first + boost::filesystem::path p(str); + std::string filename = p.filename().string(); + p.remove_filename(); + + std::string folder = p.string(); + // GenOBHash already converts to lowercase and replaces file separators but not for path + boost::algorithm::to_lower(folder); + std::replace(folder.begin(), folder.end(), '/', '\\'); + + std::uint64_t hash = GenOBHash(folder, filename); + + std::map::const_iterator it = mFiles.find(hash); +#if defined (TEST_UNIQUE_HASH) + if (it != mFiles.end() && it->second.fileName == str) +#else + if (it != mFiles.end()) +#endif + return true; + else + return getFileRecord(str).offset != -1; +} + +void TES4BSAFile::open(const std::string& file) +{ + mFilename = file; + readHeader(); +} + +Ogre::DataStreamPtr TES4BSAFile::getFile(const std::string& file) +{ + assert(file); + + FileRecord fileRec = getFileRecord(file); + if(fileRec.offset == -1) + fail("File not found: " + std::string(file)); + + boost::filesystem::ifstream input(boost::filesystem::path(mFilename), std::ios_base::binary); + input.seekg(fileRec.offset); + + std::string fullPath; + if (mEmbeddedFileNames) + getBZString(fullPath, input); // TODO: maybe cache the hash and/or offset of frequently used ones? + + if (( mCompressedByDefault && (fileRec.size & (1<<30)) == 0) + || + (!mCompressedByDefault && (fileRec.size & (1<<30)) != 0)) + { + std::uint32_t bufSize = 0; + boost::scoped_array inBuf; + inBuf.reset(new unsigned char[fileRec.size-4]); + input.read(reinterpret_cast(&bufSize), 4); + input.read(reinterpret_cast(inBuf.get()), fileRec.size-4); + Ogre::MemoryDataStream *outBuf = new Ogre::MemoryDataStream(bufSize); + Ogre::SharedPtr streamPtr(outBuf); + + int ret; + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = bufSize; + strm.next_in = inBuf.get(); + ret = inflateInit(&strm); + if (ret != Z_OK) + throw std::runtime_error("TES4BSAFile::getFile - inflateInit failed"); + + strm.avail_out = bufSize; + strm.next_out = outBuf->getPtr(); + ret = inflate(&strm, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR && "TES4BSAFile::getFile - inflate - state clobbered"); + switch (ret) + { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm); + throw std::runtime_error("TES4BSAFile::getFile - inflate failed"); + } + assert(ret == Z_OK || ret == Z_STREAM_END); + inflateEnd(&strm); + + return streamPtr; + } + else // not compressed TODO: not tested + { + Ogre::MemoryDataStream *outBuf = new Ogre::MemoryDataStream(fileRec.size); + Ogre::SharedPtr streamPtr(outBuf); + input.read(reinterpret_cast(outBuf->getPtr()), fileRec.size); + + return streamPtr; + } +} diff --git a/components/bsa/tes4bsa_file.hpp b/components/bsa/tes4bsa_file.hpp new file mode 100644 index 000000000..979a6af1a --- /dev/null +++ b/components/bsa/tes4bsa_file.hpp @@ -0,0 +1,103 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (bsa_file.h) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + TES4 stuff added by cc9cii 2018 + + */ + +#ifndef BSA_TES4BSA_FILE_H +#define BSA_TES4BSA_FILE_H + +#include +#include +#include +#include + +#include + + +namespace Bsa +{ + class TES4BSAFile + { + public: + struct FileRecord + { + std::uint32_t size; + std::uint32_t offset; + + std::string fileName; // NOTE: for testing hash collision only, see TEST_UNIQUE_HASH + + FileRecord() : size(0), offset(-1) {} + }; + + private: + /// Filenames string buffer + std::vector mStringBuf; + + /// True when an archive has been loaded + bool isLoaded; + + bool mCompressedByDefault; + bool mEmbeddedFileNames; + + std::map mFiles; + typedef std::map FileList; + + struct FolderRecord + { + std::uint32_t count; + std::uint32_t offset; + std::map files; + }; + std::map mFolders; + + FileRecord getFileRecord(const std::string& str) const; + + /// Used for error messages and getting files + std::string mFilename; + + /// Error handling + void fail(const std::string &msg); + + /// Read header information from the input source + void readHeader(); + + public: + TES4BSAFile() + : isLoaded(false), mCompressedByDefault(false), mEmbeddedFileNames(false) + { } + + /// Open an archive file. + void open(const std::string &file); + + /// Check if a file exists + bool exists(const std::string& file) const; + + Ogre::DataStreamPtr getFile(const std::string& file); + + /// Get a list of all files + const FileList &getList() const // FIXME + { return mFiles; } + }; +} + +#endif diff --git a/extern/BSAOpt/CMakeLists.txt b/extern/BSAOpt/CMakeLists.txt new file mode 100644 index 000000000..a7f1dcf74 --- /dev/null +++ b/extern/BSAOpt/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.8) + +# This is NOT intended as a stand-alone build system! Instead, you should include this from the main CMakeLists of your project. + +set(BSAOPTHASH_LIBRARY "bsaopthash") + +# Sources +set(SOURCE_FILES + hash.cpp +) + +add_library(${BSAOPTHASH_LIBRARY} STATIC ${SOURCE_FILES}) + +set(BSAOPTHASH_LIBRARIES ${BSAOPTHASH_LIBRARY}) + +link_directories(${CMAKE_CURRENT_BINARY_DIR}) +set(BSAOPTHASH_LIBRARIES ${BSAOPTHASH_LIBRARIES} PARENT_SCOPE) diff --git a/extern/BSAOpt/hash.cpp b/extern/BSAOpt/hash.cpp new file mode 100644 index 000000000..029c54279 --- /dev/null +++ b/extern/BSAOpt/hash.cpp @@ -0,0 +1,108 @@ +/* Version: MPL 1.1/LGPL 3.0 + * + * "The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is BSAopt. + * + * The Initial Developer of the Original Code is + * Ethatron . Portions created by The Initial + * Developer are Copyright (C) 2011 The Initial Developer. + * All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU Library General Public License Version 3 license (the + * "LGPL License"), in which case the provisions of LGPL License are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of the LGPL License and not + * to allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL License. + * If you do not delete the provisions above, a recipient may use your + * version of this file under either the MPL or the LGPL License." + */ + +#include +#include + +#include "hash.hpp" + +std::uint32_t GenOBHashStr(const std::string& s) { + std::uint32_t hash = 0; + + for (std::size_t i = 0; i < s.length(); i++) { + hash *= 0x1003F; + hash += (unsigned char)s[i]; + } + + return hash; +} + +std::uint64_t GenOBHashPair(const std::string& fle, const std::string& ext) { + std::uint64_t hash = 0; + + if (fle.length() > 0) { + hash = (std::uint64_t)( + (((unsigned char)fle[fle.length() - 1]) * 0x1) + + ((fle.length() > 2 ? (unsigned char)fle[fle.length() - 2] : (unsigned char)0) * 0x100) + + (fle.length() * 0x10000) + + (((unsigned char)fle[0]) * 0x1000000) + ); + + if (fle.length() > 3) { + hash += (std::uint64_t)(GenOBHashStr(fle.substr(1, fle.length() - 3)) * 0x100000000); + } + } + + if (ext.length() > 0) { + hash += (std::uint64_t)(GenOBHashStr(ext) * 0x100000000LL); + + unsigned char i = 0; + if (ext == ".nif") i = 1; + if (ext == ".kf" ) i = 2; + if (ext == ".dds") i = 3; + if (ext == ".wav") i = 4; + + if (i != 0) { + unsigned char a = (unsigned char)(((i & 0xfc ) << 5) + (unsigned char)((hash & 0xff000000) >> 24)); + unsigned char b = (unsigned char)(((i & 0xfe ) << 6) + (unsigned char)( hash & 0x000000ff) ); + unsigned char c = (unsigned char)(( i << 7) + (unsigned char)((hash & 0x0000ff00) >> 8)); + + hash -= hash & 0xFF00FFFF; + hash += (std::uint32_t)((a << 24) + b + (c << 8)); + } + } + + return hash; +} + +std::uint64_t GenOBHash(const std::string& path, std::string& file) { + std::transform(file.begin(), file.end(), file.begin(), ::tolower); + std::replace(file.begin(), file.end(), '/', '\\'); + + std::string fle; + std::string ext; + + const char *_fle = file.data(); + const char *_ext = strrchr(_fle, '.'); + if (_ext) { + ext = file.substr((0 + _ext) - _fle); + fle = file.substr(0, ( _ext) - _fle); + } + else { + ext = ""; + fle = file; + } + + if (path.length() && fle.length()) + return GenOBHashPair(path + "\\" + fle, ext); + else + return GenOBHashPair(path + fle, ext); +} diff --git a/extern/BSAOpt/hash.hpp b/extern/BSAOpt/hash.hpp new file mode 100644 index 000000000..cd936530a --- /dev/null +++ b/extern/BSAOpt/hash.hpp @@ -0,0 +1,42 @@ +/* Version: MPL 1.1/LGPL 3.0 + * + * "The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is BSAopt. + * + * The Initial Developer of the Original Code is + * Ethatron . Portions created by The Initial + * Developer are Copyright (C) 2011 The Initial Developer. + * All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU Library General Public License Version 3 license (the + * "LGPL License"), in which case the provisions of LGPL License are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of the LGPL License and not + * to allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL License. + * If you do not delete the provisions above, a recipient may use your + * version of this file under either the MPL or the LGPL License." + */ +#ifndef BSAOPT_HASH_H +#define BSAOPT_HASH_H + +#include + +std::uint32_t GenOBHashStr(const std::string& s); + +std::uint64_t GenOBHashPair(const std::string& fle, const std::string& ext); + +std::uint64_t GenOBHash(const std::string& path, std::string& file); + +#endif // BSAOPT_HASH_H diff --git a/plugins/mygui_resource_plugin/CMakeLists.txt b/plugins/mygui_resource_plugin/CMakeLists.txt index 72965c917..6ffa5bc90 100644 --- a/plugins/mygui_resource_plugin/CMakeLists.txt +++ b/plugins/mygui_resource_plugin/CMakeLists.txt @@ -28,5 +28,7 @@ set_target_properties(${MYGUI_RESOURCE_PLUGIN_LIBRARY} PROPERTIES PREFIX "") target_link_libraries(${MYGUI_RESOURCE_PLUGIN_LIBRARY} ${OGRE_LIBRARIES} ${MYGUI_LIBRARIES} + ${BSAOPTHASH_LIBRARIES} # components/bsa + ${ZLIB_LIBRARIES} # components/bsa components ) From 5b65b2171a28470d7379b29e3fb6b1be05579c52 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 6 Oct 2018 18:39:06 +1000 Subject: [PATCH 360/441] README update. --- README.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 95380e39b..32fe426de 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Font Licenses: Wrong Way, Go Back ------------------ -This is a fork of an old version of OpenMW. This version is probably not what you are looking for. It is still stuck on Ogre 1.9 and hence does not have any of the recent graphics enhancements. I suggest you use the [official release](https://github.com/OpenMW/openmw) instead. +This is a fork of an old version of OpenMW. This version is probably not what you are looking for. It is still stuck on Ogre 1.10 and hence does not have any of the recent graphics enhancements. I suggest you use the [official release](https://github.com/OpenMW/openmw) instead. Getting Started --------------- @@ -106,11 +106,9 @@ Command line options Changes ------- -Some of the differences with the official release are listed below. They are almost all to do with OpenCS. +Some of the differences with the official release are listed below. Initial changes are mostly to do with OpenCS: * Various minor bug fixes. -* Experimental support of loading TES4/TES5 records (coming soon). -* Experimental support of navMesh (coming soon). * C++11 features are used (or at least those available on MSVC 2013 onwards). * Loading time improvements. * Loading progress bar changes. @@ -125,3 +123,21 @@ Some of the differences with the official release are listed below. They are al * User preferences setting to workaround some X window managers not keeping pre-maximised state. * Use opencs.ini to store window states between sessions. +Enhancements for both OpenMW and OpenCS: + +* Hash based lookup for TES3 BSA files. +* TES4/TES5 BSA support. +* Experimental support of loading TES4/TES5 records (coming soon). +* Experimental support of NavMesh (eventually). + +Build Dependencies +------------------ + +The development was done using MSVC 2013 then MS Visual Studio Community 2015, Version 14.0.25431.01 Update 3. The code may or may not compile under linux/gcc (probably not). + +* bost-1_63 +* Ogre 1.10.0 +* ffmpeg-20140823-git-7444cf9-win64-dev +* zlib 1.2.8 +* Qt5 + From 15d5cdf3cf73ce636b566c5ea5a71293a44656d1 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 6 Oct 2018 20:38:53 +1000 Subject: [PATCH 361/441] Allow TES4 ESM/ESP to co-exist with TES3 ESM/ESP. This change aims to allow TES4/TE5 content to OpenMW. i.e. a standalone TES4 would be implemented quite differently. That said, the key changes are: * Use pointers rather than references for ESM readers so that they can be switched to another variant on the fly. * Content file dependencies to be checked within each group (only 3 groups for now, TES3/TES4/TES5) --- README.md | 17 +++++ apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwworld/cells.cpp | 2 +- apps/openmw/mwworld/cells.hpp | 4 +- apps/openmw/mwworld/cellstore.cpp | 16 ++-- apps/openmw/mwworld/cellstore.hpp | 8 +- apps/openmw/mwworld/contentloader.hpp | 2 +- apps/openmw/mwworld/esmloader.cpp | 51 ++++++++++--- apps/openmw/mwworld/esmloader.hpp | 12 +-- apps/openmw/mwworld/esmstore.cpp | 74 +++++++++++-------- apps/openmw/mwworld/worldimp.cpp | 44 ++++++++--- apps/openmw/mwworld/worldimp.hpp | 4 +- components/esm/esmcommon.hpp | 11 ++- components/esm/esmreader.cpp | 101 ++++++++++++++++++++++++-- components/esm/esmreader.hpp | 20 ++--- 15 files changed, 276 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 32fe426de..579f8c161 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,23 @@ Enhancements for both OpenMW and OpenCS: * Experimental support of loading TES4/TES5 records (coming soon). * Experimental support of NavMesh (eventually). +openmw.cfg example +------------------ + + ... + fallback-archive=Morrowind.bsa + fallback-archive=Tribunal.bsa + fallback-archive=Bloodmoon.bsa + fallback-archive=TR_Data.bsa + fallback-tes4archive=Oblivion - Meshes.bsa + #fallback-tes4archive=Skyrim - Textures.bsa + #fallback-tes4archive=Dragonborn.bsa + #fallback-tes4archive=Dawnguard.bsa + ... + data="C:/Program Files (x86)/Bethesda Softworks/Morrowind/Data Files" + data="C:/Program Files (x86)/Bethesda Softworks/Oblivion/Data" + ... + Build Dependencies ------------------ diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c110e94d6..43b3913b2 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -134,7 +134,7 @@ namespace MWBase virtual const MWWorld::ESMStore& getStore() const = 0; - virtual std::vector& getEsmReader() = 0; + virtual std::vector& getEsmReader() = 0; virtual MWWorld::LocalScripts& getLocalScripts() = 0; diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 2aa817fa5..e8a785b36 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -83,7 +83,7 @@ void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const writer.endRecord (ESM::REC_CSTA); } -MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector& reader) +MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector >& reader) : mStore (store), mReader (reader), mIdCache (40, std::pair ("", (CellStore*)0)), /// \todo make cache size configurable mIdCacheIndex (0) diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index 0b7d9444f..7f5adc487 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -28,7 +28,7 @@ namespace MWWorld class Cells { const MWWorld::ESMStore& mStore; - std::vector& mReader; + std::vector >& mReader; mutable std::map mInteriors; mutable std::map, CellStore> mExteriors; std::vector > mIdCache; @@ -47,7 +47,7 @@ namespace MWWorld void clear(); - Cells (const MWWorld::ESMStore& store, std::vector& reader); + Cells (const MWWorld::ESMStore& store, std::vector >& reader); CellStore *getExterior (int x, int y); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 0e392f055..c087957b2 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -400,7 +400,7 @@ namespace MWWorld + mNpcs.mList.size(); } - void CellStore::load (const MWWorld::ESMStore &store, std::vector &esm) + void CellStore::load (const MWWorld::ESMStore &store, std::vector > &esm) { if (mState!=State_Loaded) { @@ -417,7 +417,7 @@ namespace MWWorld } } - void CellStore::preload (const MWWorld::ESMStore &store, std::vector &esm) + void CellStore::preload (const MWWorld::ESMStore &store, std::vector > &esm) { if (mState==State_Unloaded) { @@ -427,7 +427,7 @@ namespace MWWorld } } - void CellStore::listRefs(const MWWorld::ESMStore &store, std::vector &esm) + void CellStore::listRefs(const MWWorld::ESMStore &store, std::vector > &esm) { assert (mCell); @@ -439,13 +439,13 @@ namespace MWWorld { // Reopen the ESM reader and seek to the right position. int index = mCell->mContextList.at(i).index; - mCell->restore (esm[index], i); + mCell->restore (*esm[0][index], (int)i); // FIXME: hardcoded 0 means TES3 ESM::CellRef ref; // Get each reference in turn bool deleted = false; - while (mCell->getNextRef (esm[index], ref, deleted)) + while (mCell->getNextRef (*esm[0][index], ref, deleted)) // FIXME hardcoded 0 means TES3 { if (deleted) continue; @@ -472,7 +472,7 @@ namespace MWWorld std::sort (mIds.begin(), mIds.end()); } - void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector &esm) + void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector > &esm) { assert (mCell); @@ -484,14 +484,14 @@ namespace MWWorld { // Reopen the ESM reader and seek to the right position. int index = mCell->mContextList.at(i).index; - mCell->restore (esm[index], i); + mCell->restore (*esm[0][index], (int)i); // FIXME: hardcoded 0 means TES3 ESM::CellRef ref; ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; // Get each reference in turn bool deleted = false; - while(mCell->getNextRef(esm[index], ref, deleted)) + while(mCell->getNextRef(*esm[0][index], ref, deleted)) // FIXME: 0 means TES3 { // Don't load reference if it was moved to a different cell. ESM::MovedCellRefTracker::const_iterator iter = diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 672b6046b..689f07d4f 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -112,10 +112,10 @@ namespace MWWorld int count() const; ///< Return total number of references, including deleted ones. - void load (const MWWorld::ESMStore &store, std::vector &esm); + void load (const MWWorld::ESMStore &store, std::vector > &esm); ///< Load references from content file. - void preload (const MWWorld::ESMStore &store, std::vector &esm); + void preload (const MWWorld::ESMStore &store, std::vector > &esm); ///< Build ID list from content file. /// Call functor (ref) for each reference. functor must return a bool. Returning @@ -213,9 +213,9 @@ namespace MWWorld } /// Run through references and store IDs - void listRefs(const MWWorld::ESMStore &store, std::vector &esm); + void listRefs(const MWWorld::ESMStore &store, std::vector > &esm); - void loadRefs(const MWWorld::ESMStore &store, std::vector &esm); + void loadRefs(const MWWorld::ESMStore &store, std::vector > &esm); void loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store); ///< Make case-adjustments to \a ref and insert it into the respective container. diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index 46bd7d3f9..e589d6d05 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -21,7 +21,7 @@ struct ContentLoader { } - virtual void load(const boost::filesystem::path& filepath, int& index) + virtual void load(const boost::filesystem::path& filepath, std::vector >& contentFiles) { std::cout << "Loading content file " << filepath.string() << std::endl; mListener.setLabel(filepath.string()); diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index b12d646e7..e87ad8a04 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -6,7 +6,7 @@ namespace MWWorld { -EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& readers, +EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector >& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener) : ContentLoader(listener) , mEsm(readers) @@ -15,17 +15,46 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read { } -void EsmLoader::load(const boost::filesystem::path& filepath, int& index) +// FIXME: tesVerIndex stuff is rather clunky, needs to be refactored +void EsmLoader::load(const boost::filesystem::path& filepath, std::vector >& contentFiles) { - ContentLoader::load(filepath.filename(), index); - - ESM::ESMReader lEsm; - lEsm.setEncoder(mEncoder); - lEsm.setIndex(index); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string()); - mEsm[index] = lEsm; - mStore.load(mEsm[index], &mListener); + int tesVerIndex = 0; // FIXME: hard coded, 0 = MW, 1 = TES4, 2 = TES5 (TODO: Fallout) + int index = 0; + + ContentLoader::load(filepath.filename(), contentFiles); // set the label on the loading bar + + ESM::ESMReader *lEsm = new ESM::ESMReader(); + lEsm->setEncoder(mEncoder); + lEsm->setGlobalReaderList(&mEsm[tesVerIndex]); // global reader list is used by ESMStore::load only + lEsm->open(filepath.string()); + + int esmVer = lEsm->getVer(); + bool isTes4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + bool isTes5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_17; + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + if (isTes4 || isTes5 || isFONV) + { + if (isTes4) + tesVerIndex = 1; + else if (isTes5) + tesVerIndex = 2; + else + tesVerIndex = 3; + + // do nothing for now + return; + } + else + { + tesVerIndex = 0; // 0 = MW + index = contentFiles[tesVerIndex].size(); + contentFiles[tesVerIndex].push_back(filepath.filename().string()); + lEsm->setIndex(index); + mEsm[tesVerIndex].push_back(lEsm); + } + + mStore.load(*mEsm[tesVerIndex][index], &mListener); } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index b96af707c..32c2e977a 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -22,15 +22,15 @@ class ESMStore; struct EsmLoader : public ContentLoader { - EsmLoader(MWWorld::ESMStore& store, std::vector& readers, - ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); + EsmLoader(MWWorld::ESMStore& store, std::vector >& readers, + ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); - void load(const boost::filesystem::path& filepath, int& index); + void load(const boost::filesystem::path& filepath, std::vector >& contentFiles); private: - std::vector& mEsm; - MWWorld::ESMStore& mStore; - ToUTF8::Utf8Encoder* mEncoder; + std::vector >& mEsm; // Note: the ownership of the readers is with the caller + MWWorld::ESMStore& mStore; + ToUTF8::Utf8Encoder* mEncoder; }; } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 91233cf70..1b05cb19b 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -31,38 +31,52 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) ESM::Dialogue *dialogue = 0; - // Land texture loading needs to use a separate internal store for each plugin. - // We set the number of plugins here to avoid continual resizes during loading, - // and so we can properly verify if valid plugin indices are being passed to the - // LandTexture Store retrieval methods. - mLandTextures.resize(esm.getGlobalReaderList()->size()); - - /// \todo Move this to somewhere else. ESMReader? - // Cache parent esX files by tracking their indices in the global list of - // all files/readers used by the engine. This will greaty accelerate - // refnumber mangling, as required for handling moved references. - const std::vector &masters = esm.getGameFiles(); - std::vector *allPlugins = esm.getGlobalReaderList(); - for (size_t j = 0; j < masters.size(); j++) { - ESM::Header::MasterData &mast = const_cast(masters[j]); - std::string fname = mast.name; - int index = ~0; - for (int i = 0; i < esm.getIndex(); i++) { - const std::string &candidate = allPlugins->at(i).getContext().filename; - std::string fnamecandidate = boost::filesystem::path(candidate).filename().string(); - if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { - index = i; - break; + int esmVer = esm.getVer(); + bool isTes4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + bool isTes5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_17; + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + // FIXME: temporary workaround + if (!(isTes4 || isTes5 || isFONV)) // MW only + { + // Land texture loading needs to use a separate internal store for each plugin. + // We set the number of plugins here to avoid continual resizes during loading, + // and so we can properly verify if valid plugin indices are being passed to the + // LandTexture Store retrieval methods. + mLandTextures.resize(esm.getGlobalReaderList()->size()); // FIXME: size should be for MW only + } + + // FIXME: for TES4/TES5 whether a dependent file is loaded is already checked in + // ESM4::Reader::updateModIndicies() which is called in EsmLoader::load() before this + if (!(isTes4 || isTes5 || isFONV)) // MW only + { + /// \todo Move this to somewhere else. ESMReader? + // Cache parent esX files by tracking their indices in the global list of + // all files/readers used by the engine. This will greaty accelerate + // refnumber mangling, as required for handling moved references. + const std::vector &masters = esm.getGameFiles(); + std::vector *allPlugins = esm.getGlobalReaderList(); + for (size_t j = 0; j < masters.size(); j++) { + ESM::Header::MasterData &mast = const_cast(masters[j]); + std::string fname = mast.name; + int index = ~0; + for (int i = 0; i < esm.getIndex(); i++) { + const std::string &candidate = allPlugins->at(i)->getContext().filename; + std::string fnamecandidate = boost::filesystem::path(candidate).filename().string(); + if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { + index = i; + break; + } } + if (index == (int)~0) { + // Tried to load a parent file that has not been loaded yet. This is bad, + // the launcher should have taken care of this. + std::string fstring = "File " + esm.getName() + " asks for parent file " + masters[j].name + + ", but it has not been loaded yet. Please check your load order."; + esm.fail(fstring); + } + mast.index = index; } - if (index == (int)~0) { - // Tried to load a parent file that has not been loaded yet. This is bad, - // the launcher should have taken care of this. - std::string fstring = "File " + esm.getName() + " asks for parent file " + masters[j].name - + ", but it has not been loaded yet. Please check your load order."; - esm.fail(fstring); - } - mast.index = index; } // Loop through all records diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 71cf4a143..c15fff497 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -90,12 +90,12 @@ namespace MWWorld return mLoaders.insert(std::make_pair(extension, loader)).second; } - void load(const boost::filesystem::path& filepath, int& index) + void load(const boost::filesystem::path& filepath, std::vector >& contentFiles) { LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { - it->second->load(filepath, index); + it->second->load(filepath, contentFiles); } else { @@ -169,7 +169,7 @@ namespace MWWorld mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); - mEsm.resize(contentFiles.size()); + mEsm.resize(3); // FIXME: 0 - TES3, 1 - TES4, 2 - TES5 (TODO: Fallout) Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); @@ -187,7 +187,7 @@ namespace MWWorld listener->loadingOff(); // insert records that may not be present in all versions of MW - if (mEsm[0].getFormat() == 0) + if (mEsm[0][0]->getFormat() == 0) // FIXME: first file may not be for MW ensureNeededRecords(); mStore.setUp(); @@ -474,6 +474,13 @@ namespace MWWorld delete mPhysics; delete mPlayer; + + for (unsigned int i = 0; i < mEsm.size(); ++i) + for (unsigned int j = 0; j < mEsm[i].size(); ++j) + { + mEsm[i][j]->close(); + delete mEsm[i][j]; + } } const ESM::Cell *World::getExterior (const std::string& cellName) const @@ -542,9 +549,9 @@ namespace MWWorld return mStore; } - std::vector& World::getEsmReader() + std::vector& World::getEsmReader() { - return mEsm; + return mEsm[0]; // FIXME: only MW for now (but doesn't seem to be used anywhere?) } LocalScripts& World::getLocalScripts() @@ -2609,18 +2616,35 @@ namespace MWWorld return mScriptsEnabled; } + // The aim is to allow loading various types of TES files in any combination, as long as + // the dependent files are loaded first. To achieve this, separate indicies for each TES + // versions are required. + // + // The trouble is that until the file is opened by an ESM reader to check the version from + // the header we don't know which index to increment. + // + // One option is to allow the content loader to manage. + + // FIXME: Appears to be loading all the files named in 'content' located in fileCollections + // based on the extension string (e.g. .esm). This probably means that the contents are in + // the correct load order. + // + // 'contentLoader' has a number of loaders that can deal with various extension types. void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ContentLoader& contentLoader) { + std::vector > contentFiles; + contentFiles.resize(3); + std::vector::const_iterator it(content.begin()); std::vector::const_iterator end(content.end()); - for (int idx = 0; it != end; ++it, ++idx) + for (; it != end; ++it) { boost::filesystem::path filename(*it); const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(*it)) { - contentLoader.load(col.getPath(*it), idx); + contentLoader.load(col.getPath(*it), contentFiles); } else { @@ -2937,7 +2961,7 @@ namespace MWWorld MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false); action.execute(ptr); } - + void World::updateWeather(float duration, bool paused) { if (mPlayer->wasTeleported()) @@ -2945,7 +2969,7 @@ namespace MWWorld mPlayer->setTeleported(false); mWeatherManager->switchToNextWeather(true); } - + mWeatherManager->update(duration, paused); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index bf25c20bb..0324ce873 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -66,7 +66,7 @@ namespace MWWorld MWWorld::Scene *mWorldScene; MWWorld::Player *mPlayer; - std::vector mEsm; + std::vector > mEsm; MWWorld::ESMStore mStore; LocalScripts mLocalScripts; MWWorld::Globals mGlobalVariables; @@ -199,7 +199,7 @@ namespace MWWorld virtual const MWWorld::ESMStore& getStore() const; - virtual std::vector& getEsmReader(); + virtual std::vector& getEsmReader(); virtual LocalScripts& getLocalScripts(); diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index d90a3444d..0c0419f3a 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -11,8 +11,15 @@ namespace ESM { enum Version { - VER_12 = 0x3f99999a, - VER_13 = 0x3fa66666 + VER_12 = 0x3f99999a, + VER_13 = 0x3fa66666, + VER_080 = 0x3f4ccccd, // TES4 + VER_100 = 0x3f800000, // TES4 + VER_132 = 0x3fa8f5c3, // FONV Courier's Stash, DeadMoney + VER_133 = 0x3faa3d71, // FONV HonestHearts + VER_134 = 0x3fab851f, // FONV, GunRunnersArsenal, LonesomeRoad, OldWorldBlues + VER_094 = 0x3f70a3d7, // TES5/FO3 + VER_17 = 0x3fd9999a // TES5 }; /* A structure used for holding fixed-length strings. In the case of diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index b4b44f876..1ae7bf259 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -71,12 +71,103 @@ void ESMReader::open(Ogre::DataStreamPtr _esm, const std::string &name) { openRaw(_esm, name); - if (getRecName() != "TES3") - fail("Not a valid Morrowind file"); - - getRecHeader(); + NAME modVer = getRecName(); + if (modVer == "TES3") + { + getRecHeader(); - mHeader.load (*this); + mHeader.load (*this); + } + else if (modVer == "TES4") + { + mHeader.mData.author.assign(""); + mHeader.mData.desc.assign(""); + char buf[512]; // arbitrary number + unsigned short size; + + skip(16); // skip the rest of the header, note it may be 4 bytes longer + + NAME rec = getRecName(); + if (rec != "HEDR") + rec = getRecName(); // adjust for extra 4 bytes + bool readRec = true; + + while (mEsm->size() - mEsm->tell() >= 4) // Shivering Isle or Bashed Patch can end here + { + if (!readRec) // may be already read + rec = getRecName(); + else + readRec = false; + + switch (rec.val) + { + case 0x52444548: // HEDR + { + skip(2); // data size + getT(mHeader.mData.version); + getT(mHeader.mData.records); + skip(4); // skip next available object id + break; + } + case 0x4d414e43: // CNAM + { + getT(size); + getExact(buf, size); + std::string author; + size = std::min(size, (unsigned short)32); // clamp for TES3 format + author.assign(buf, size - 1); // don't copy null terminator + mHeader.mData.author.assign(author); + break; + } + case 0x4d414e53: // SNAM + { + getT(size); + getExact(buf, size); + std::string desc; + size = std::min(size, (unsigned short)256); // clamp for TES3 format + desc.assign(buf, size - 1); // don't copy null terminator + mHeader.mData.desc.assign(desc); + break; + } + case 0x5453414d: // MAST + { + Header::MasterData m; + getT(size); + getExact(buf, size); + m.name.assign(buf, size-1); // don't copy null terminator + + rec = getRecName(); + if (rec == "DATA") + { + getT(size); + getT(m.size); // 64 bits + } + else + { + // some esp's don't have DATA subrecord + m.size = 0; + readRec = true; // don't read again at the top of while loop + } + mHeader.mMaster.push_back (m); + break; + } + case 0x56544e49: // INTV + case 0x43434e49: // INCC + case 0x4d414e4f: // ONAM + { + getT(size); + skip(size); + break; + } + case 0x50555247: // GRUP + default: + return; // all done + } + } + return; + } + else + fail("Not a valid Morrowind file"); } void ESMReader::open(const std::string &file) diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 3a2f32640..d2d92b23b 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -23,6 +23,7 @@ class ESMReader public: ESMReader(); + virtual ~ESMReader() {} /************************************************************************* * @@ -74,9 +75,9 @@ public: void openRaw(const std::string &file); /// Get the file size. Make sure that the file has been opened! - size_t getFileSize() { return mEsm->size(); } + virtual size_t getFileSize() { return mEsm->size(); } /// Get the current position in the file. Make sure that the file has been opened! - size_t getFileOffset() { return mEsm->tell(); } + virtual size_t getFileOffset() { return mEsm->tell(); } // This is a quick hack for multiple esm/esp files. Each plugin introduces its own // terrain palette, but ESMReader does not pass a reference to the correct plugin @@ -86,8 +87,8 @@ public: void setIndex(const int index) {mIdx = index; mCtx.index = index;} int getIndex() {return mIdx;} - void setGlobalReaderList(std::vector *list) {mGlobalReaderList = list;} - std::vector *getGlobalReaderList() {return mGlobalReaderList;} + void setGlobalReaderList(std::vector *list) {mGlobalReaderList = list;} + std::vector *getGlobalReaderList() {return mGlobalReaderList;} /************************************************************************* * @@ -292,8 +293,6 @@ public: private: Ogre::DataStreamPtr mEsm; - ESM_Context mCtx; - unsigned int mRecordFlags; // Special file signifier (see SpecialFile enum above) @@ -301,10 +300,13 @@ private: // Buffer for ESM strings std::vector mBuffer; - Header mHeader; - - std::vector *mGlobalReaderList; + std::vector *mGlobalReaderList; ToUTF8::Utf8Encoder* mEncoder; + +protected: + ESM_Context mCtx; + + Header mHeader; }; } #endif From 5ad440cb45ad5ecd75276e2e82b8ed4ad4fcbcd5 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 7 Oct 2018 07:54:57 +1100 Subject: [PATCH 362/441] TES4/TES5 ESM/ESP file reader. To support the possibility of a standalone implementation of TES4, the ESM/ESP code is placed in the 'extern' folder. Much more work needs to be done. --- extern/esm4/CMakeLists.txt | 80 ++++ extern/esm4/achr.cpp | 117 +++++ extern/esm4/achr.hpp | 64 +++ extern/esm4/acre.cpp | 93 ++++ extern/esm4/acre.hpp | 64 +++ extern/esm4/acti.cpp | 107 +++++ extern/esm4/acti.hpp | 63 +++ extern/esm4/alch.cpp | 112 +++++ extern/esm4/alch.hpp | 71 +++ extern/esm4/ammo.cpp | 139 ++++++ extern/esm4/ammo.hpp | 78 ++++ extern/esm4/anio.cpp | 80 ++++ extern/esm4/anio.hpp | 62 +++ extern/esm4/appa.cpp | 113 +++++ extern/esm4/appa.hpp | 73 +++ extern/esm4/armo.cpp | 166 +++++++ extern/esm4/armo.hpp | 105 +++++ extern/esm4/book.cpp | 141 ++++++ extern/esm4/book.hpp | 110 +++++ extern/esm4/cell.cpp | 268 +++++++++++ extern/esm4/cell.hpp | 113 +++++ extern/esm4/clas.cpp | 103 ++++ extern/esm4/clas.hpp | 65 +++ extern/esm4/clot.cpp | 100 ++++ extern/esm4/clot.hpp | 103 ++++ extern/esm4/common.cpp | 111 +++++ extern/esm4/common.hpp | 933 +++++++++++++++++++++++++++++++++++++ extern/esm4/cont.cpp | 115 +++++ extern/esm4/cont.hpp | 68 +++ extern/esm4/crea.cpp | 223 +++++++++ extern/esm4/crea.hpp | 98 ++++ extern/esm4/door.cpp | 101 ++++ extern/esm4/door.hpp | 75 +++ extern/esm4/eyes.cpp | 82 ++++ extern/esm4/eyes.hpp | 67 +++ extern/esm4/flor.cpp | 97 ++++ extern/esm4/flor.hpp | 77 +++ extern/esm4/formid.cpp | 76 +++ extern/esm4/formid.hpp | 42 ++ extern/esm4/furn.cpp | 106 +++++ extern/esm4/furn.hpp | 63 +++ extern/esm4/gras.cpp | 78 ++++ extern/esm4/gras.hpp | 97 ++++ extern/esm4/grup.hpp | 157 +++++++ extern/esm4/hair.cpp | 83 ++++ extern/esm4/hair.hpp | 70 +++ extern/esm4/idle.cpp | 84 ++++ extern/esm4/idle.hpp | 61 +++ extern/esm4/ingr.cpp | 137 ++++++ extern/esm4/ingr.hpp | 82 ++++ extern/esm4/keym.cpp | 100 ++++ extern/esm4/keym.hpp | 73 +++ extern/esm4/land.cpp | 222 +++++++++ extern/esm4/land.hpp | 134 ++++++ extern/esm4/ligh.cpp | 127 +++++ extern/esm4/ligh.hpp | 89 ++++ extern/esm4/ltex.cpp | 106 +++++ extern/esm4/ltex.hpp | 74 +++ extern/esm4/lvlc.cpp | 105 +++++ extern/esm4/lvlc.hpp | 62 +++ extern/esm4/lvli.cpp | 103 ++++ extern/esm4/lvli.hpp | 61 +++ extern/esm4/mato.cpp | 76 +++ extern/esm4/mato.hpp | 57 +++ extern/esm4/misc.cpp | 102 ++++ extern/esm4/misc.hpp | 72 +++ extern/esm4/navi.cpp | 373 +++++++++++++++ extern/esm4/navi.hpp | 116 +++++ extern/esm4/navm.cpp | 268 +++++++++++ extern/esm4/navm.hpp | 111 +++++ extern/esm4/npc_.cpp | 237 ++++++++++ extern/esm4/npc_.hpp | 115 +++++ extern/esm4/race.cpp | 229 +++++++++ extern/esm4/race.hpp | 71 +++ extern/esm4/reader.cpp | 560 ++++++++++++++++++++++ extern/esm4/reader.hpp | 278 +++++++++++ extern/esm4/refr.cpp | 269 +++++++++++ extern/esm4/refr.hpp | 78 ++++ extern/esm4/regn.cpp | 152 ++++++ extern/esm4/regn.hpp | 84 ++++ extern/esm4/sbsp.cpp | 78 ++++ extern/esm4/sbsp.hpp | 63 +++ extern/esm4/sgst.cpp | 118 +++++ extern/esm4/sgst.hpp | 74 +++ extern/esm4/slgm.cpp | 99 ++++ extern/esm4/slgm.hpp | 75 +++ extern/esm4/soun.cpp | 82 ++++ extern/esm4/soun.hpp | 86 ++++ extern/esm4/stat.cpp | 101 ++++ extern/esm4/stat.hpp | 63 +++ extern/esm4/tes4.cpp | 123 +++++ extern/esm4/tes4.hpp | 84 ++++ extern/esm4/tree.cpp | 85 ++++ extern/esm4/tree.hpp | 61 +++ extern/esm4/weap.cpp | 194 ++++++++ extern/esm4/weap.hpp | 83 ++++ extern/esm4/wrld.cpp | 179 +++++++ extern/esm4/wrld.hpp | 122 +++++ 98 files changed, 12047 insertions(+) create mode 100644 extern/esm4/CMakeLists.txt create mode 100644 extern/esm4/achr.cpp create mode 100644 extern/esm4/achr.hpp create mode 100644 extern/esm4/acre.cpp create mode 100644 extern/esm4/acre.hpp create mode 100644 extern/esm4/acti.cpp create mode 100644 extern/esm4/acti.hpp create mode 100644 extern/esm4/alch.cpp create mode 100644 extern/esm4/alch.hpp create mode 100644 extern/esm4/ammo.cpp create mode 100644 extern/esm4/ammo.hpp create mode 100644 extern/esm4/anio.cpp create mode 100644 extern/esm4/anio.hpp create mode 100644 extern/esm4/appa.cpp create mode 100644 extern/esm4/appa.hpp create mode 100644 extern/esm4/armo.cpp create mode 100644 extern/esm4/armo.hpp create mode 100644 extern/esm4/book.cpp create mode 100644 extern/esm4/book.hpp create mode 100644 extern/esm4/cell.cpp create mode 100644 extern/esm4/cell.hpp create mode 100644 extern/esm4/clas.cpp create mode 100644 extern/esm4/clas.hpp create mode 100644 extern/esm4/clot.cpp create mode 100644 extern/esm4/clot.hpp create mode 100644 extern/esm4/common.cpp create mode 100644 extern/esm4/common.hpp create mode 100644 extern/esm4/cont.cpp create mode 100644 extern/esm4/cont.hpp create mode 100644 extern/esm4/crea.cpp create mode 100644 extern/esm4/crea.hpp create mode 100644 extern/esm4/door.cpp create mode 100644 extern/esm4/door.hpp create mode 100644 extern/esm4/eyes.cpp create mode 100644 extern/esm4/eyes.hpp create mode 100644 extern/esm4/flor.cpp create mode 100644 extern/esm4/flor.hpp create mode 100644 extern/esm4/formid.cpp create mode 100644 extern/esm4/formid.hpp create mode 100644 extern/esm4/furn.cpp create mode 100644 extern/esm4/furn.hpp create mode 100644 extern/esm4/gras.cpp create mode 100644 extern/esm4/gras.hpp create mode 100644 extern/esm4/grup.hpp create mode 100644 extern/esm4/hair.cpp create mode 100644 extern/esm4/hair.hpp create mode 100644 extern/esm4/idle.cpp create mode 100644 extern/esm4/idle.hpp create mode 100644 extern/esm4/ingr.cpp create mode 100644 extern/esm4/ingr.hpp create mode 100644 extern/esm4/keym.cpp create mode 100644 extern/esm4/keym.hpp create mode 100644 extern/esm4/land.cpp create mode 100644 extern/esm4/land.hpp create mode 100644 extern/esm4/ligh.cpp create mode 100644 extern/esm4/ligh.hpp create mode 100644 extern/esm4/ltex.cpp create mode 100644 extern/esm4/ltex.hpp create mode 100644 extern/esm4/lvlc.cpp create mode 100644 extern/esm4/lvlc.hpp create mode 100644 extern/esm4/lvli.cpp create mode 100644 extern/esm4/lvli.hpp create mode 100644 extern/esm4/mato.cpp create mode 100644 extern/esm4/mato.hpp create mode 100644 extern/esm4/misc.cpp create mode 100644 extern/esm4/misc.hpp create mode 100644 extern/esm4/navi.cpp create mode 100644 extern/esm4/navi.hpp create mode 100644 extern/esm4/navm.cpp create mode 100644 extern/esm4/navm.hpp create mode 100644 extern/esm4/npc_.cpp create mode 100644 extern/esm4/npc_.hpp create mode 100644 extern/esm4/race.cpp create mode 100644 extern/esm4/race.hpp create mode 100644 extern/esm4/reader.cpp create mode 100644 extern/esm4/reader.hpp create mode 100644 extern/esm4/refr.cpp create mode 100644 extern/esm4/refr.hpp create mode 100644 extern/esm4/regn.cpp create mode 100644 extern/esm4/regn.hpp create mode 100644 extern/esm4/sbsp.cpp create mode 100644 extern/esm4/sbsp.hpp create mode 100644 extern/esm4/sgst.cpp create mode 100644 extern/esm4/sgst.hpp create mode 100644 extern/esm4/slgm.cpp create mode 100644 extern/esm4/slgm.hpp create mode 100644 extern/esm4/soun.cpp create mode 100644 extern/esm4/soun.hpp create mode 100644 extern/esm4/stat.cpp create mode 100644 extern/esm4/stat.hpp create mode 100644 extern/esm4/tes4.cpp create mode 100644 extern/esm4/tes4.hpp create mode 100644 extern/esm4/tree.cpp create mode 100644 extern/esm4/tree.hpp create mode 100644 extern/esm4/weap.cpp create mode 100644 extern/esm4/weap.hpp create mode 100644 extern/esm4/wrld.cpp create mode 100644 extern/esm4/wrld.hpp diff --git a/extern/esm4/CMakeLists.txt b/extern/esm4/CMakeLists.txt new file mode 100644 index 000000000..9ce5a9660 --- /dev/null +++ b/extern/esm4/CMakeLists.txt @@ -0,0 +1,80 @@ +# +# Copyright (C) 2016-2018 cc9cii +# +# This software is provided 'as-is', without any express or implied +# warranty. In no event will the authors be held liable for any damages +# arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# 3. This notice may not be removed or altered from any source distribution. +# +# cc9cii cc9c@iinet.net.au +# + +set(ESM4_LIBRARY "esm4") + +set(ESM4_SOURCE_FILES + common.cpp + tes4.cpp + navi.cpp + wrld.cpp + navm.cpp + land.cpp + ltex.cpp + cell.cpp + regn.cpp + stat.cpp + refr.cpp + anio.cpp + cont.cpp + misc.cpp + acti.cpp + armo.cpp + npc_.cpp + flor.cpp + gras.cpp + tree.cpp + ligh.cpp + achr.cpp + book.cpp + furn.cpp + soun.cpp + weap.cpp + door.cpp + clot.cpp + alch.cpp + ammo.cpp + appa.cpp + ingr.cpp + sgst.cpp + slgm.cpp + keym.cpp + hair.cpp + eyes.cpp + crea.cpp + lvlc.cpp + lvli.cpp + acre.cpp + idle.cpp + mato.cpp + sbsp.cpp + race.cpp + clas.cpp + formid.cpp + reader.cpp +) + +add_library(${ESM4_LIBRARY} STATIC ${ESM4_SOURCE_FILES}) +set(ESM4_LIBRARIES ${ESM4_LIBRARY}) + +link_directories(${CMAKE_CURRENT_BINARY_DIR}) +set(ESM4_LIBRARIES ${ESM4_LIBRARIES} PARENT_SCOPE) diff --git a/extern/esm4/achr.cpp b/extern/esm4/achr.cpp new file mode 100644 index 000000000..3a3a35ebf --- /dev/null +++ b/extern/esm4/achr.cpp @@ -0,0 +1,117 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "achr.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::ActorCharacter::ActorCharacter() : mFormId(0), mFlags(0), mDisabled(false), mBaseObj(0), + mScale(1.f), mOwner(0), mGlobal(0) +{ + mEditorId.clear(); + mFullName.clear(); + + mEsp.parent = 0; + mEsp.flags = 0; +} + +ESM4::ActorCharacter::~ActorCharacter() +{ +} + +void ESM4::ActorCharacter::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_NAME: reader.getFormId(mBaseObj); break; + case ESM4::SUB_DATA: reader.get(mPosition); break; + case ESM4::SUB_XSCL: reader.get(mScale); break; + case ESM4::SUB_XOWN: reader.get(mOwner); break; + case ESM4::SUB_XESP: + { + reader.get(mEsp); + reader.adjustFormId(mEsp.parent); + break; + } + case ESM4::SUB_XRGD: // ragdoll + case ESM4::SUB_XHRS: // horse formId + case ESM4::SUB_XMRC: // merchant container formId + // TES5 + case ESM4::SUB_XAPD: // activation parent + case ESM4::SUB_XAPR: // active parent + case ESM4::SUB_XEZN: // encounter zone + case ESM4::SUB_XHOR: + case ESM4::SUB_XLCM: // leveled creature + case ESM4::SUB_XLCN: // location + case ESM4::SUB_XLKR: // location route? + case ESM4::SUB_XLRT: // location type + // + case ESM4::SUB_XPRD: + case ESM4::SUB_XPPA: + case ESM4::SUB_INAM: + case ESM4::SUB_PDTO: + // + case ESM4::SUB_XRGB: + case ESM4::SUB_XIS2: + case ESM4::SUB_XPCI: // formId + case ESM4::SUB_XLOD: + case ESM4::SUB_VMAD: + case ESM4::SUB_XLRL: // Unofficial Skyrim Patch + case ESM4::SUB_XRDS: // FO3 + case ESM4::SUB_XIBS: // FO3 + case ESM4::SUB_SCHR: // FO3 + case ESM4::SUB_TNAM: // FO3 + case ESM4::SUB_XATO: // FONV + { + //std::cout << "ACHR " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ACHR::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::ActorCharacter::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::ActorCharacter::blank() +//{ +//} diff --git a/extern/esm4/achr.hpp b/extern/esm4/achr.hpp new file mode 100644 index 000000000..86e2640a9 --- /dev/null +++ b/extern/esm4/achr.hpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ACHR_H +#define ESM4_ACHR_H + +#include "common.hpp" // Position + +namespace ESM4 +{ + class Reader; + class Writer; + + struct ActorCharacter + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + FormId mBaseObj; + + Position mPosition; + float mScale; // default 1.f + FormId mOwner; + FormId mGlobal; + + bool mDisabled; + EnableParent mEsp; // TODO may need to check mFlags & 0x800 (initially disabled) + + ActorCharacter(); + virtual ~ActorCharacter(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ACHR_H diff --git a/extern/esm4/acre.cpp b/extern/esm4/acre.cpp new file mode 100644 index 000000000..e83f6dda9 --- /dev/null +++ b/extern/esm4/acre.cpp @@ -0,0 +1,93 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "acre.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::ActorCreature::ActorCreature() : mFormId(0), mFlags(0), mDisabled(false), mBaseObj(0), mScale(1.f), + mOwner(0), mGlobal(0), mFactionRank(0) +{ + mEditorId.clear(); + + mEsp.parent = 0; + mEsp.flags = 0; +} + +ESM4::ActorCreature::~ActorCreature() +{ +} + +void ESM4::ActorCreature::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_NAME: reader.getFormId(mBaseObj); break; + case ESM4::SUB_DATA: reader.get(mPosition); break; + case ESM4::SUB_XSCL: reader.get(mScale); break; + case ESM4::SUB_XESP: + { + reader.get(mEsp); + reader.adjustFormId(mEsp.parent); + break; + } + case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; + case ESM4::SUB_XGLB: reader.get(mGlobal); break; // FIXME: formId? + case ESM4::SUB_XRNK: reader.get(mFactionRank); break; + case ESM4::SUB_XRGD: // ragdoll + case ESM4::SUB_XLKR: // FO3 + case ESM4::SUB_XLCM: // FO3 + case ESM4::SUB_XEZN: // FO3 + case ESM4::SUB_XRGB: // FO3 + { + //std::cout << "ACRE " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ACRE::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::ActorCreature::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::ActorCreature::blank() +//{ +//} diff --git a/extern/esm4/acre.hpp b/extern/esm4/acre.hpp new file mode 100644 index 000000000..2b43ab7a1 --- /dev/null +++ b/extern/esm4/acre.hpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ACRE_H +#define ESM4_ACRE_H + +#include "common.hpp" // EnableParent + +namespace ESM4 +{ + class Reader; + class Writer; + + struct ActorCreature + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + FormId mBaseObj; + + Position mPosition; + float mScale; // default 1.f + FormId mOwner; + FormId mGlobal; + std::uint32_t mFactionRank; + + bool mDisabled; + EnableParent mEsp; // TODO may need to check mFlags & 0x800 (initially disabled) + + ActorCreature(); + virtual ~ActorCreature(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ACRE_H diff --git a/extern/esm4/acti.cpp b/extern/esm4/acti.cpp new file mode 100644 index 000000000..8016d6e47 --- /dev/null +++ b/extern/esm4/acti.cpp @@ -0,0 +1,107 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "acti.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Activator::Activator() : mFormId(0), mFlags(0), mScript(0), mSound(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Activator::~Activator() +{ +} + +void ESM4::Activator::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("ACTI FULL data read error"); + + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_SNAM: reader.getFormId(mSound); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: + case ESM4::SUB_DEST: + case ESM4::SUB_DMDL: + case ESM4::SUB_DMDS: + case ESM4::SUB_DMDT: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: + case ESM4::SUB_FNAM: + case ESM4::SUB_KNAM: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_OBND: + case ESM4::SUB_PNAM: + case ESM4::SUB_RNAM: + case ESM4::SUB_VMAD: + case ESM4::SUB_VNAM: + case ESM4::SUB_WNAM: + case ESM4::SUB_INAM: // FONV + case ESM4::SUB_XATO: // FONV + { + //std::cout << "ACTI " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ACTI::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Activator::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Activator::blank() +//{ +//} diff --git a/extern/esm4/acti.hpp b/extern/esm4/acti.hpp new file mode 100644 index 000000000..00b87408c --- /dev/null +++ b/extern/esm4/acti.hpp @@ -0,0 +1,63 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ACTI_H +#define ESM4_ACTI_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Activator + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + FormId mScript; + FormId mSound; + + float mBoundRadius; + + Activator(); + virtual ~Activator(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ACTI_H diff --git a/extern/esm4/alch.cpp b/extern/esm4/alch.cpp new file mode 100644 index 000000000..0aaffa340 --- /dev/null +++ b/extern/esm4/alch.cpp @@ -0,0 +1,112 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "alch.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Potion::Potion() : mFormId(0), mFlags(0), mScript(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.weight = 0.f; + + std::memset(&mEffect, 0, sizeof(ScriptEffect)); +} + +ESM4::Potion::~Potion() +{ +} + +void ESM4::Potion::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("ALCH FULL data read error"); + + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_SCIT: + { + reader.get(mEffect); + reader.adjustFormId(mEffect.formId); + break; + } + case ESM4::SUB_MODT: + case ESM4::SUB_ENIT: + case ESM4::SUB_EFID: + case ESM4::SUB_EFIT: + case ESM4::SUB_CTDA: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_YNAM: + case ESM4::SUB_ZNAM: + case ESM4::SUB_ETYP: // FO3 + case ESM4::SUB_MICO: // FO3 + { + //std::cout << "ALCH " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ALCH::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Potion::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Potion::blank() +//{ +//} diff --git a/extern/esm4/alch.hpp b/extern/esm4/alch.hpp new file mode 100644 index 000000000..f0493e4ee --- /dev/null +++ b/extern/esm4/alch.hpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ALCH_H +#define ESM4_ALCH_H + +#include "common.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Potion + { +#pragma pack(push, 1) + struct Data + { + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + FormId mScript; + ScriptEffect mEffect; + + float mBoundRadius; + + Data mData; + + Potion(); + virtual ~Potion(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ALCH_H diff --git a/extern/esm4/ammo.cpp b/extern/esm4/ammo.cpp new file mode 100644 index 000000000..05cec0c2a --- /dev/null +++ b/extern/esm4/ammo.cpp @@ -0,0 +1,139 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "ammo.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Ammo::Ammo() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); +} + +ESM4::Ammo::~Ammo() +{ +} + +void ESM4::Ammo::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM4::VER_132 || esmVer == ESM4::VER_133 || esmVer == ESM4::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("AMMO FULL data read error"); + + break; + } + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM4::VER_094 || reader.esmVersion() == ESM4::VER_170) + if (subHdr.dataSize == 16) // FO3 has 13 bytes even though VER_094 + { + FormId projectile; + reader.get(projectile); // FIXME: add to mData + reader.get(mData.flags); + reader.get(mData.weight); + float damageInFloat; + reader.get(damageInFloat); // FIXME: add to mData + } + else if (isFONV || subHdr.dataSize == 13) + { + reader.get(mData.speed); + std::uint8_t flags; + reader.get(flags); + mData.flags = flags; + static std::uint8_t dummy; + reader.get(dummy); + reader.get(dummy); + reader.get(dummy); + reader.get(mData.value); + reader.get(mData.clipRounds); + } + else // TES4 + { + reader.get(mData.speed); + reader.get(mData.flags); + reader.get(mData.value); + reader.get(mData.weight); + reader.get(mData.damage); + } + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + case ESM4::SUB_YNAM: + case ESM4::SUB_ZNAM: + case ESM4::SUB_DESC: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_MICO: // FO3 + case ESM4::SUB_ONAM: // FO3 + case ESM4::SUB_DAT2: // FONV + case ESM4::SUB_QNAM: // FONV + case ESM4::SUB_RCIL: // FONV + case ESM4::SUB_SCRI: // FONV + { + //std::cout << "AMMO " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::AMMO::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Ammo::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Ammo::blank() +//{ +//} diff --git a/extern/esm4/ammo.hpp b/extern/esm4/ammo.hpp new file mode 100644 index 000000000..6d9171bee --- /dev/null +++ b/extern/esm4/ammo.hpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_AMMO_H +#define ESM4_AMMO_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Ammo + { + struct Data // FIXME: TES5 projectile, damage (float) + { + float speed; + std::uint32_t flags; + std::uint32_t value; // gold + float weight; + std::uint16_t damage; + std::uint8_t clipRounds; // only in FO3/FONV + + Data() : speed(0.f), flags(0), value(0), weight(0.f), damage(0), clipRounds(0) {} + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + Ammo(); + virtual ~Ammo(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_AMMO_H diff --git a/extern/esm4/anio.cpp b/extern/esm4/anio.cpp new file mode 100644 index 000000000..bab68c069 --- /dev/null +++ b/extern/esm4/anio.cpp @@ -0,0 +1,80 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "anio.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::AnimObject::AnimObject() : mFormId(0), mFlags(0), mBoundRadius(0.f), mIdleAnim(0) +{ + mEditorId.clear(); + mModel.clear(); + mUnloadEvent.clear(); +} + +ESM4::AnimObject::~AnimObject() +{ +} + +void ESM4::AnimObject::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_BNAM: reader.getZString(mUnloadEvent); break; + case ESM4::SUB_DATA: reader.getFormId(mIdleAnim); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: // TES5 only + case ESM4::SUB_MODS: // TES5 only + { + //std::cout << "ANIO " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ANIO::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::AnimObject::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::AnimObject::blank() +//{ +//} diff --git a/extern/esm4/anio.hpp b/extern/esm4/anio.hpp new file mode 100644 index 000000000..9ee349a1c --- /dev/null +++ b/extern/esm4/anio.hpp @@ -0,0 +1,62 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ANIO_H +#define ESM4_ANIO_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct AnimObject + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + + FormId mIdleAnim; // only in TES4 + std::string mUnloadEvent; // only in TES5 + + AnimObject(); + virtual ~AnimObject(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ANIO_H diff --git a/extern/esm4/appa.cpp b/extern/esm4/appa.cpp new file mode 100644 index 000000000..9041e2ecd --- /dev/null +++ b/extern/esm4/appa.cpp @@ -0,0 +1,113 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "appa.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Apparatus::Apparatus() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScript(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.type = 0; + mData.value = 0; + mData.weight = 0.f; + mData.quality = 0.f; +} + +ESM4::Apparatus::~Apparatus() +{ +} + +void ESM4::Apparatus::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("APPA FULL data read error"); + + break; + } + case ESM4::SUB_DATA: + { + if (reader.esmVersion() == ESM4::VER_094 || reader.esmVersion() == ESM4::VER_170) + { + reader.get(mData.value); + reader.get(mData.weight); + } + else + { + reader.get(mData.type); + reader.get(mData.value); + reader.get(mData.weight); + reader.get(mData.quality); + } + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_DESC: + case ESM4::SUB_OBND: + case ESM4::SUB_QUAL: + { + //std::cout << "APPA " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::APPAPPAoad - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Apparatus::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Apparatus::blank() +//{ +//} diff --git a/extern/esm4/appa.hpp b/extern/esm4/appa.hpp new file mode 100644 index 000000000..88bc4b953 --- /dev/null +++ b/extern/esm4/appa.hpp @@ -0,0 +1,73 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_APPA_H +#define ESM4_APPA_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Apparatus + { + struct Data + { + std::uint8_t type; // 0 = Mortar and Pestle, 1 = Alembic, 2 = Calcinator, 3 = Retort + std::uint32_t value; // gold + float weight; + float quality; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + FormId mScript; + + Data mData; + + Apparatus(); + virtual ~Apparatus(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_APPA_H diff --git a/extern/esm4/armo.cpp b/extern/esm4/armo.cpp new file mode 100644 index 000000000..1557f7c64 --- /dev/null +++ b/extern/esm4/armo.cpp @@ -0,0 +1,166 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "armo.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Armor::Armor() : mFormId(0), mFlags(0), mBoundRadius(0.f), mArmorFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIconMale.clear(); + mIconFemale.clear(); + + mData.armor = 0; + mData.value = 0; + mData.health = 0; + mData.weight = 0.f; +} + +ESM4::Armor::~Armor() +{ +} + +void ESM4::Armor::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM4::VER_132 || esmVer == ESM4::VER_133 || esmVer == ESM4::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("ARMO FULL data read error"); + + break; + } + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM4::VER_094 || reader.esmVersion() == ESM4::VER_170) + if (subHdr.dataSize == 8) // FO3 has 12 bytes even though VER_094 + { + reader.get(mData.value); + reader.get(mData.weight); + } + else if (isFONV || subHdr.dataSize == 12) + { + reader.get(mData.value); + reader.get(mData.health); + reader.get(mData.weight); + } + else + reader.get(mData); // TES4 + + break; + } + case ESM4::SUB_MODL: // seems only for Dawnguard/Dragonborn? + { + if (!reader.getZString(mModel)) + throw std::runtime_error ("ARMO MODL data read error"); + + break; + } + case ESM4::SUB_ICON: reader.getZString(mIconMale); break; + case ESM4::SUB_ICO2: reader.getZString(mIconFemale); break; + //case ESM4::SUB_BMDT: reader.get(mArmorFlags); break; // see below re. FO3 + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MOD2: + case ESM4::SUB_MOD3: + case ESM4::SUB_MOD4: + case ESM4::SUB_MO2B: + case ESM4::SUB_MO3B: + case ESM4::SUB_MO4B: + case ESM4::SUB_MO2T: + case ESM4::SUB_MO2S: + case ESM4::SUB_MO3T: + case ESM4::SUB_MO4T: + case ESM4::SUB_MO4S: + case ESM4::SUB_OBND: + case ESM4::SUB_BODT: + case ESM4::SUB_BOD2: + case ESM4::SUB_YNAM: + case ESM4::SUB_ZNAM: + case ESM4::SUB_RNAM: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_DESC: + case ESM4::SUB_TNAM: + case ESM4::SUB_DNAM: + case ESM4::SUB_BAMT: + case ESM4::SUB_BIDS: + case ESM4::SUB_ETYP: + case ESM4::SUB_BMCT: + case ESM4::SUB_MICO: + case ESM4::SUB_MIC2: + case ESM4::SUB_EAMT: + case ESM4::SUB_EITM: + case ESM4::SUB_VMAD: + case ESM4::SUB_BMDT: // FO3 FIXME might have different format + case ESM4::SUB_REPL: // FO3 + case ESM4::SUB_BIPL: // FO3 + case ESM4::SUB_MODD: // FO3 + case ESM4::SUB_MOSD: // FO3 + case ESM4::SUB_MODS: // FO3 + case ESM4::SUB_MO3S: // FO3 + case ESM4::SUB_BNAM: // FONV + case ESM4::SUB_SNAM: // FONV + { + //std::cout << "ARMO " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ARMO::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Armor::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Armor::blank() +//{ +//} diff --git a/extern/esm4/armo.hpp b/extern/esm4/armo.hpp new file mode 100644 index 000000000..a697baf6c --- /dev/null +++ b/extern/esm4/armo.hpp @@ -0,0 +1,105 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ARMO_H +#define ESM4_ARMO_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Armor + { + enum Flags + { + // Biped Object Flags + Flag_Head = 0x00000001, + Flag_Hair = 0x00000002, + Flag_UpperBody = 0x00000004, + Flag_LowerBody = 0x00000008, + Flag_Hand = 0x00000010, + Flag_Foot = 0x00000020, + Flag_RightRing = 0x00000040, + Flag_LeftRing = 0x00000080, + Flag_Amulet = 0x00000100, + Flag_Weapon = 0x00000200, + Flag_BackWeapon = 0x00000400, + Flag_SideWeapon = 0x00000800, + Flag_Quiver = 0x00001000, + Flag_Shield = 0x00002000, + Flag_Torch = 0x00004000, + Flag_Tail = 0x00008000, + // General Flags + Flag_HideRings = 0x00010000, + Flag_HideAmulet = 0x00020000, + Flag_NonPlayable = 0x00400000, + Flag_Unknown = 0xCD000000 + }; + +#pragma pack(push, 1) + struct Data + { + std::uint16_t armor; // Only in TES4? + std::uint32_t value; + std::uint32_t health; // not in TES5? + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIconMale; + std::string mIconFemale; + + float mBoundRadius; + + std::uint32_t mArmorFlags; + FormId mScript; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + Armor(); + virtual ~Armor(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ARMO_H diff --git a/extern/esm4/book.cpp b/extern/esm4/book.cpp new file mode 100644 index 000000000..9bc9dad56 --- /dev/null +++ b/extern/esm4/book.cpp @@ -0,0 +1,141 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "book.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Book::Book() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScript(0), + mEnchantmentPoints(0), mEnchantment(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mText.clear(); + mIcon.clear(); + + mData.flags = 0; + mData.type = 0; + mData.bookSkill = 0; + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::Book::~Book() +{ +} + +void ESM4::Book::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("BOOK FULL data read error"); + + break; + } + case ESM4::SUB_DESC: + { + if (reader.hasLocalizedStrings()) + { + std::uint32_t formid; + reader.get(formid); + if (formid) + reader.getLocalizedString(formid, mText); // sometimes formid is null + } + else if (!reader.getZString(mText)) + throw std::runtime_error ("BOOK DESC data read error"); + + break; + } + case ESM4::SUB_DATA: + { + reader.get(mData.flags); + //if (reader.esmVersion() == ESM4::VER_094 || reader.esmVersion() == ESM4::VER_170) + if (subHdr.dataSize == 16) // FO3 has 10 bytes even though VER_094 + { + static std::uint8_t dummy; + reader.get(mData.type); + reader.get(dummy); + reader.get(dummy); + reader.get(mData.teaches); + } + else + { + reader.get(mData.bookSkill); + } + reader.get(mData.value); + reader.get(mData.weight); + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_CNAM: + case ESM4::SUB_INAM: + case ESM4::SUB_YNAM: + case ESM4::SUB_VMAD: + { + //std::cout << "BOOK " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::BOOK::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Book::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Book::blank() +//{ +//} diff --git a/extern/esm4/book.hpp b/extern/esm4/book.hpp new file mode 100644 index 000000000..897a93257 --- /dev/null +++ b/extern/esm4/book.hpp @@ -0,0 +1,110 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_BOOK_H +#define ESM4_BOOK_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Book + { + enum Flags + { + Flag_Scroll = 0x0001, + Flag_NoTake = 0x0002 + }; + + enum BookSkill // for TES4 only + { + BookSkill_None = -1, + BookSkill_Armorer = 0, + BookSkill_Athletics = 1, + BookSkill_Blade = 2, + BookSkill_Block = 3, + BookSkill_Blunt = 4, + BookSkill_HandToHand = 5, + BookSkill_HeavyArmor = 6, + BookSkill_Alchemy = 7, + BookSkill_Alteration = 8, + BookSkill_Conjuration = 9, + BookSkill_Destruction = 10, + BookSkill_Illusion = 11, + BookSkill_Mysticism = 12, + BookSkill_Restoration = 13, + BookSkill_Acrobatics = 14, + BookSkill_LightArmor = 15, + BookSkill_Marksman = 16, + BookSkill_Mercantile = 17, + BookSkill_Security = 18, + BookSkill_Sneak = 19, + BookSkill_Speechcraft = 20 + }; + + struct Data + { + std::uint8_t flags; + std::uint8_t type; // TES5 only + std::uint32_t teaches; // TES5 only + std::int8_t bookSkill; // not in TES5 + std::uint32_t value; + float weight; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + std::string mText; + FormId mScript; + std::string mIcon; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + Book(); + virtual ~Book(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_BOOK_H diff --git a/extern/esm4/cell.cpp b/extern/esm4/cell.cpp new file mode 100644 index 000000000..8ecbe1b70 --- /dev/null +++ b/extern/esm4/cell.cpp @@ -0,0 +1,268 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "cell.hpp" + +#include +#include + +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +ESM4::Cell::Cell() : mParent(0), mFormId(0), mFlags(0), mCellFlags(0), mX(0), mY(0), mOwner(0), + mGlobal(0), mClimate(0), mWater(0), mWaterHeight(0.f), mPreloaded(false) +{ + mEditorId.clear(); + mFullName.clear(); + + mLighting.ambient = 0; + mLighting.directional = 0; + mLighting.fogNear = 0; + mLighting.unknown1 = 0.f; + mLighting.unknown2 = 0.f; + mLighting.unknown3 = 0; + mLighting.unknown4 = 0; + mLighting.unknown5 = 0.f; + mLighting.unknown6 = 0.f; +} + +ESM4::Cell::~Cell() +{ +} + +void ESM4::Cell::init(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + mParent = reader.currWorld(); + + reader.clearCellGrid(); // clear until XCLC FIXME: somehow do this automatically? + + // Sometimes cell 0,0 does not have an XCLC sub record (e.g. ToddLand 000009BF) + // To workaround this issue put a default value if group is "exterior sub cell" and its + // grid from label is "0 0". Note the reversed X/Y order (no matter since they're both 0 + // anyway). + if (reader.grp().type == ESM4::Grp_ExteriorSubCell + && reader.grp().label.grid[1] == 0 && reader.grp().label.grid[0] == 0) + { + ESM4::CellGrid currCellGrid; + currCellGrid.grid.x = 0; + currCellGrid.grid.y = 0; + reader.setCurrCellGrid(currCellGrid); // side effect: sets mCellGridValid true + } +} + +// TODO: Try loading only EDID and XCLC (along with mFormId, mFlags and mParent) +// +// But, for external cells we may be scanning the whole record since we don't know if there is +// going to be an EDID subrecord. And the vast majority of cells are these kinds. +// +// So perhaps some testing needs to be done to see if scanning and skipping takes +// longer/shorter/same as loading the subrecords. +bool ESM4::Cell::preload(ESM4::Reader& reader) +{ + if (!mPreloaded) + load(reader); + + mPreloaded = true; + //return reader.skipNextGroupCellChild(); // true if found cell child group and skipped + return true; // FIXME +} + +void ESM4::Cell::load(ESM4::Reader& reader) +{ + if (mPreloaded) + return; + + // FIXME: we may need to call setCurrCell (and maybe setCurrCellGrid?) again before loading + // cell child groups if we are loading them after restoring the context + init(reader); + reader.setCurrCell(mFormId); // save for LAND (and other children) to access later + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM4::VER_132 || esmVer == ESM4::VER_133 || esmVer == ESM4::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: + { + if (!reader.getZString(mEditorId)) + throw std::runtime_error ("CELL EDID data read error"); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "CELL Editor ID: " << mEditorId << std::endl; +#endif + break; + } + case ESM4::SUB_XCLC: + { + //(X, Y) grid location of the cell followed by flags. Always in + //exterior cells and never in interior cells. + // + // int32 - X + // int32 - Y + // uint32 - flags (high bits look random) + // + // 0x1 - Force Hide Land Quad 1 + // 0x2 - Force Hide Land Quad 2 + // 0x4 - Force Hide Land Quad 3 + // 0x8 - Force Hide Land Quad 4 + uint32_t flags; + reader.get(mX); + reader.get(mY); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "CELL group " << ESM4::printLabel(reader.grp().label, reader.grp().type) << std::endl; + std::cout << padding << "CELL formId " << std::hex << reader.hdr().record.id << std::endl; + std::cout << padding << "CELL X " << std::dec << mX << ", Y " << mY << std::endl; +#endif + if (esmVer == ESM4::VER_094 || esmVer == ESM4::VER_170 || isFONV) + if (subHdr.dataSize == 12) + reader.get(flags); // not in Obvlivion, nor FO3/FONV + + // Remember cell grid for later (loading LAND, NAVM which should be CELL temporary children) + // Note that grids only apply for external cells. For interior cells use the cell's formid. + ESM4::CellGrid currCell; + currCell.grid.x = (int16_t)mX; + currCell.grid.y = (int16_t)mY; + reader.setCurrCellGrid(currCell); + + break; + } + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("CELL FULL data read error"); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "Name: " << mFullName << std::endl; +#endif + break; + } + case ESM4::SUB_DATA: + { + if (esmVer == ESM4::VER_094 || esmVer == ESM4::VER_170 || isFONV) + if (subHdr.dataSize == 2) + reader.get(mCellFlags); + else + { + assert(subHdr.dataSize == 1 && "CELL unexpected DATA flag size"); + reader.get((std::uint8_t&)mCellFlags); + } + else + { + reader.get((std::uint8_t&)mCellFlags); // 8 bits in Obvlivion + } +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "flags: " << std::hex << mCellFlags << std::endl; +#endif + break; + } + case ESM4::SUB_XCLR: + { + mRegions.resize(subHdr.dataSize/sizeof(FormId)); + for (std::vector::iterator it = mRegions.begin(); it != mRegions.end(); ++it) + { + reader.getFormId(*it); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "region: " << std::hex << *it << std::endl; +#endif + } + break; + } + case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; + case ESM4::SUB_XGLB: reader.getFormId(mGlobal); break; // Oblivion only? + case ESM4::SUB_XCCM: reader.getFormId(mClimate); break; + case ESM4::SUB_XCWT: reader.getFormId(mWater); break; + case ESM4::SUB_XCLW: reader.get(mWaterHeight); break; + case ESM4::SUB_XCLL: + { + // 92 bytes for TES5, 19*4 = 76 bytes for FO3/FONV + if (esmVer == ESM4::VER_094 || esmVer == ESM4::VER_170 || isFONV) + reader.skipSubRecordData(); + else + { + assert(subHdr.dataSize == 36 && "CELL lighting size error"); + reader.get(mLighting); + } + break; + } + case ESM4::SUB_TVDT: + case ESM4::SUB_MHDT: + case ESM4::SUB_XCGD: + case ESM4::SUB_LTMP: + case ESM4::SUB_LNAM: + case ESM4::SUB_XNAM: + case ESM4::SUB_XLCN: + case ESM4::SUB_XWCS: + case ESM4::SUB_XWCU: + case ESM4::SUB_XWCN: + case ESM4::SUB_XCAS: + case ESM4::SUB_XCIM: + case ESM4::SUB_XCMO: + case ESM4::SUB_XEZN: + case ESM4::SUB_XWEM: + case ESM4::SUB_XILL: + case ESM4::SUB_XCMT: // Oblivion only? + case ESM4::SUB_XRNK: // Oblivion only? + case ESM4::SUB_XCET: // FO3 + case ESM4::SUB_IMPF: // FO3 Zeta + { + //std::cout << "CELL " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CELL::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Cell::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::Cell::blank() +{ +} diff --git a/extern/esm4/cell.hpp b/extern/esm4/cell.hpp new file mode 100644 index 000000000..3b1ab22fb --- /dev/null +++ b/extern/esm4/cell.hpp @@ -0,0 +1,113 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CELL_H +#define ESM4_CELL_H + +#include +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + struct ReaderContext; + struct CellGroup; + typedef std::uint32_t FormId; + + enum CellFlags // TES4 TES5 + { // ----------------------- ------------------------------------ + CELL_Interior = 0x0001, // Can't travel from here Interior + CELL_HasWater = 0x0002, // Has water Has Water + CELL_NoTravel = 0x0004, // not Can't Travel From Here(Int only) + CELL_HideLand = 0x0008, // Force hide land (Ext) No LOD Water + // Oblivion interior (Int) + CELL_Public = 0x0020, // Public place Public Area + CELL_HandChgd = 0x0040, // Hand changed Hand Changed + CELL_QuasiExt = 0x0080, // Behave like exterior Show Sky + CELL_SkyLight = 0x0100 // Use Sky Lighting + }; + + // Unlike TES3, multiple cells can have the same exterior co-ordinates. + // The cells need to be organised under world spaces. + struct Cell + { +#pragma pack(push, 1) + // TES4 (guesses only), TES5 are 96 bytes + struct Lighting + { // | Aichan Prison values + std::uint32_t ambient; // | 16 17 19 00 (RGBA) + std::uint32_t directional; // | 00 00 00 00 (RGBA) + std::uint32_t fogNear; // | 1D 1B 16 00 (RGBA) + float unknown1; // Fog Near | 00 00 00 00 = 0.f + float unknown2; // Fog Far | 00 80 3B 45 = 3000.f + std::int32_t unknown3; // rotation xy | 00 00 00 00 = 0 + std::int32_t unknown4; // rotation z | 00 00 00 00 = 0 + float unknown5; // Fog dir fade | 00 00 80 3F = 1.f + float unknown6; // Fog clip dist | 00 80 3B 45 = 3000.f + }; +#pragma pack(pop) + + FormId mParent; // world formId (for grouping cells), from the loading sequence + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::uint16_t mCellFlags; // TES5 can also be 8 bits + + std::int32_t mX; + std::int32_t mY; + + FormId mOwner; + FormId mGlobal; + FormId mClimate; + FormId mWater; + float mWaterHeight; + + std::vector mRegions; + Lighting mLighting; + + CellGroup *mCellGroup; + + Cell(); + virtual ~Cell(); + + void init(ESM4::Reader& reader); // common setup for both preload() and load() + + bool mPreloaded; + bool preload(ESM4::Reader& reader); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; +} + +#endif // ESM4_CELL_H diff --git a/extern/esm4/clas.cpp b/extern/esm4/clas.cpp new file mode 100644 index 000000000..836f11407 --- /dev/null +++ b/extern/esm4/clas.cpp @@ -0,0 +1,103 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "clas.hpp" + +#include +#include + +#include "reader.hpp" +//#include "writer.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +ESM4::Class::Class() +{ + mEditorId.clear(); + mFullName.clear(); + mDesc.clear(); + mIcon.clear(); +} + +ESM4::Class::~Class() +{ +} + +void ESM4::Class::load(ESM4::Reader& reader) +{ + //mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted? + mFormId = reader.hdr().record.id; + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("CLAS FULL data read error"); + break; + } + case ESM4::SUB_DESC: + { + if (reader.hasLocalizedStrings()) + { + std::uint32_t formid; + reader.get(formid); + if (formid) + reader.getLocalizedString(formid, mDesc); // sometimes formid is null + } + else if (!reader.getZString(mDesc)) + throw std::runtime_error ("CLAS DESC data read error"); + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: + { + //std::cout << "CLAS " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CLAS::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Class::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Class::blank() +//{ +//} diff --git a/extern/esm4/clas.hpp b/extern/esm4/clas.hpp new file mode 100644 index 000000000..0aa4bc500 --- /dev/null +++ b/extern/esm4/clas.hpp @@ -0,0 +1,65 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CLAS_H +#define ESM4_CLAS_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Class + { + struct Data + { + std::uint32_t attr; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mDesc; + std::string mIcon; + Data mData; + + Class(); + ~Class(); + + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& reader) const; + + //void blank(); + }; +} + +#endif // ESM4_CLAS_H diff --git a/extern/esm4/clot.cpp b/extern/esm4/clot.cpp new file mode 100644 index 000000000..8f9d3a5ac --- /dev/null +++ b/extern/esm4/clot.cpp @@ -0,0 +1,100 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "clot.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Clothing::Clothing() : mFormId(0), mFlags(0), mBoundRadius(0.f), mClothingFlags(0), + mScript(0), mEnchantmentPoints(0), mEnchantment(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIconMale.clear(); + mIconFemale.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::Clothing::~Clothing() +{ +} + +void ESM4::Clothing::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIconMale); break; + case ESM4::SUB_ICO2: reader.getZString(mIconFemale); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_BMDT: reader.get(mClothingFlags); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MOD2: + case ESM4::SUB_MOD3: + case ESM4::SUB_MOD4: + case ESM4::SUB_MO2B: + case ESM4::SUB_MO3B: + case ESM4::SUB_MO4B: + case ESM4::SUB_MO2T: + case ESM4::SUB_MO3T: + case ESM4::SUB_MO4T: + { + //std::cout << "CLOT " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CLOT::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Clothing::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Clothing::blank() +//{ +//} diff --git a/extern/esm4/clot.hpp b/extern/esm4/clot.hpp new file mode 100644 index 000000000..c13b8a3c7 --- /dev/null +++ b/extern/esm4/clot.hpp @@ -0,0 +1,103 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CLOT_H +#define ESM4_CLOT_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Clothing + { + enum Flags + { + // Biped Object Flags + Flag_Head = 0x00000001, + Flag_Hair = 0x00000002, + Flag_UpperBody = 0x00000004, + Flag_LowerBody = 0x00000008, + Flag_Hand = 0x00000010, + Flag_Foot = 0x00000020, + Flag_RightRing = 0x00000040, + Flag_LeftRing = 0x00000080, + Flag_Amulet = 0x00000100, + Flag_Weapon = 0x00000200, + Flag_BackWeapon = 0x00000400, + Flag_SideWeapon = 0x00000800, + Flag_Quiver = 0x00001000, + Flag_Shield = 0x00002000, + Flag_Torch = 0x00004000, + Flag_Tail = 0x00008000, + // General Flags + Flag_HideRings = 0x00010000, + Flag_HideAmulet = 0x00020000, + Flag_NonPlayable = 0x00400000, + Flag_Unknown = 0xCD000000 + }; + +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIconMale; // inventory + std::string mIconFemale; // inventory + + float mBoundRadius; + + std::uint32_t mClothingFlags; + FormId mScript; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + Clothing(); + virtual ~Clothing(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_CLOT_H diff --git a/extern/esm4/common.cpp b/extern/esm4/common.cpp new file mode 100644 index 000000000..ee9d0edb8 --- /dev/null +++ b/extern/esm4/common.cpp @@ -0,0 +1,111 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "common.hpp" + +#include +#include +#include + +#include + +#include "formid.hpp" + +namespace ESM4 +{ + const char *sGroupType[] = + { + "Record Type", "World Child", "Interior Cell", "Interior Sub Cell", "Exterior Cell", + "Exterior Sub Cell", "Cell Child", "Topic Child", "Cell Persistent Child", + "Cell Temporary Child", "Cell Visible Dist Child", "Unknown" + }; + + std::string printLabel(const GroupLabel& label, const std::uint32_t type) + { + std::ostringstream ss; + ss << std::string(sGroupType[std::min(type, (uint32_t)11)]); // avoid out of range + + switch (type) + { + case ESM4::Grp_RecordType: + { + ss << ": " << std::string((char*)label.recordType, 4); + break; + } + case ESM4::Grp_ExteriorCell: + case ESM4::Grp_ExteriorSubCell: + { + //short x, y; + //y = label & 0xff; + //x = (label >> 16) & 0xff; + ss << ": grid (x, y) " << std::dec << label.grid[1] << ", " << label.grid[0]; + + break; + } + case ESM4::Grp_InteriorCell: + case ESM4::Grp_InteriorSubCell: + { + ss << ": block 0x" << std::hex << label.value; + break; + } + case ESM4::Grp_WorldChild: + case ESM4::Grp_CellChild: + case ESM4::Grp_TopicChild: + case ESM4::Grp_CellPersistentChild: + case ESM4::Grp_CellTemporaryChild: + case ESM4::Grp_CellVisibleDistChild: + { + ss << ": FormId 0x" << formIdToString(label.value); + break; + } + default: + break; + } + + return ss.str(); + } + + std::string printName(const std::uint32_t typeId) + { + unsigned char typeName[4]; + typeName[0] = typeId & 0xff; + typeName[1] = (typeId >> 8) & 0xff; + typeName[2] = (typeId >> 16) & 0xff; + typeName[3] = (typeId >> 24) & 0xff; + + return std::string((char*)typeName, 4); + } + + void gridToString(std::int16_t x, std::int16_t y, std::string& str) + { + char buf[6+6+2+1]; // longest signed 16 bit number is 6 characters (-32768) + int res = snprintf(buf, 6+6+2+1, "#%d %d", x, y); + if (res > 0 && res < 6+6+2+1) + str.assign(buf); + else + throw std::runtime_error("possible buffer overflow while converting grid"); + } +} diff --git a/extern/esm4/common.hpp b/extern/esm4/common.hpp new file mode 100644 index 000000000..535ae981e --- /dev/null +++ b/extern/esm4/common.hpp @@ -0,0 +1,933 @@ +/* + Copyright (C) 2015-2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + MKTAG macro was adapated from ScummVM. + +*/ +#ifndef ESM4_COMMON_H +#define ESM4_COMMON_H + +#include +#include + +// From ScummVM's endianness.h but for little endian +#define MKTAG(a0,a1,a2,a3) ((std::uint32_t)((a0) | ((a1) << 8) | ((a2) << 16) | ((a3) << 24))) + +namespace ESM4 +{ + enum ESMVersions + { + VER_080 = 0x3f4ccccd, // TES4 + VER_100 = 0x3f800000, // TES4 + VER_132 = 0x3fa8f5c3, // FONV Courier's Stash, DeadMoney + VER_133 = 0x3faa3d71, // FONV HonestHearts + VER_134 = 0x3fab851f, // FONV, GunRunnersArsenal, LonesomeRoad, OldWorldBlues + VER_094 = 0x3f70a3d7, // TES5/FO3 + VER_170 = 0x3fd9999a // TES5 + }; + + // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format + enum RecordTypes + { + REC_AACT = MKTAG('A','A','C','T'), // Action + REC_ACHR = MKTAG('A','C','H','R'), // Actor Reference + REC_ACTI = MKTAG('A','C','T','I'), // Activator + REC_ADDN = MKTAG('A','D','D','N'), // Addon Node + REC_ALCH = MKTAG('A','L','C','H'), // Potion + REC_AMMO = MKTAG('A','M','M','O'), // Ammo + REC_ANIO = MKTAG('A','N','I','O'), // Animated Object + REC_APPA = MKTAG('A','P','P','A'), // Apparatus (probably unused) + REC_ARMA = MKTAG('A','R','M','A'), // Armature (Model) + REC_ARMO = MKTAG('A','R','M','O'), // Armor + REC_ARTO = MKTAG('A','R','T','O'), // Art Object + REC_ASPC = MKTAG('A','S','P','C'), // Acoustic Space + REC_ASTP = MKTAG('A','S','T','P'), // Association Type + REC_AVIF = MKTAG('A','V','I','F'), // Actor Values/Perk Tree Graphics + REC_BOOK = MKTAG('B','O','O','K'), // Book + REC_BPTD = MKTAG('B','P','T','D'), // Body Part Data + REC_CAMS = MKTAG('C','A','M','S'), // Camera Shot + REC_CELL = MKTAG('C','E','L','L'), // Cell + REC_CLAS = MKTAG('C','L','A','S'), // Class + REC_CLFM = MKTAG('C','L','F','M'), // Color + REC_CLMT = MKTAG('C','L','M','T'), // Climate + REC_CLOT = MKTAG('C','L','O','T'), // Clothing + REC_COBJ = MKTAG('C','O','B','J'), // Constructible Object (recipes) + REC_COLL = MKTAG('C','O','L','L'), // Collision Layer + REC_CONT = MKTAG('C','O','N','T'), // Container + REC_CPTH = MKTAG('C','P','T','H'), // Camera Path + REC_CREA = MKTAG('C','R','E','A'), // Creature + REC_CSTY = MKTAG('C','S','T','Y'), // Combat Style + REC_DEBR = MKTAG('D','E','B','R'), // Debris + REC_DIAL = MKTAG('D','I','A','L'), // Dialog Topic + REC_DLBR = MKTAG('D','L','B','R'), // Dialog Branch + REC_DLVW = MKTAG('D','L','V','W'), // Dialog View + REC_DOBJ = MKTAG('D','O','B','J'), // Default Object Manager + REC_DOOR = MKTAG('D','O','O','R'), // Door + REC_DUAL = MKTAG('D','U','A','L'), // Dual Cast Data (possibly unused) + //REC_ECZN = MKTAG('E','C','Z','N'), // Encounter Zone + REC_EFSH = MKTAG('E','F','S','H'), // Effect Shader + REC_ENCH = MKTAG('E','N','C','H'), // Enchantment + REC_EQUP = MKTAG('E','Q','U','P'), // Equip Slot (flag-type values) + REC_EXPL = MKTAG('E','X','P','L'), // Explosion + REC_EYES = MKTAG('E','Y','E','S'), // Eyes + REC_FACT = MKTAG('F','A','C','T'), // Faction + REC_FLOR = MKTAG('F','L','O','R'), // Flora + REC_FLST = MKTAG('F','L','S','T'), // Form List (non-leveled list) + REC_FSTP = MKTAG('F','S','T','P'), // Footstep + REC_FSTS = MKTAG('F','S','T','S'), // Footstep Set + REC_FURN = MKTAG('F','U','R','N'), // Furniture + REC_GLOB = MKTAG('G','L','O','B'), // Global Variable + REC_GMST = MKTAG('G','M','S','T'), // Game Setting + REC_GRAS = MKTAG('G','R','A','S'), // Grass + REC_GRUP = MKTAG('G','R','U','P'), // Form Group + REC_HAIR = MKTAG('H','A','I','R'), // Hair + //REC_HAZD = MKTAG('H','A','Z','D'), // Hazard + REC_HDPT = MKTAG('H','D','P','T'), // Head Part + REC_IDLE = MKTAG('I','D','L','E'), // Idle Animation + REC_IDLM = MKTAG('I','D','L','M'), // Idle Marker + REC_IMAD = MKTAG('I','M','A','D'), // Image Space Modifier + REC_IMGS = MKTAG('I','M','G','S'), // Image Space + REC_INFO = MKTAG('I','N','F','O'), // Dialog Topic Info + REC_INGR = MKTAG('I','N','G','R'), // Ingredient + REC_IPCT = MKTAG('I','P','C','T'), // Impact Data + REC_IPDS = MKTAG('I','P','D','S'), // Impact Data Set + REC_KEYM = MKTAG('K','E','Y','M'), // Key + REC_KYWD = MKTAG('K','Y','W','D'), // Keyword + REC_LAND = MKTAG('L','A','N','D'), // Land + REC_LCRT = MKTAG('L','C','R','T'), // Location Reference Type + REC_LCTN = MKTAG('L','C','T','N'), // Location + REC_LGTM = MKTAG('L','G','T','M'), // Lighting Template + REC_LIGH = MKTAG('L','I','G','H'), // Light + REC_LSCR = MKTAG('L','S','C','R'), // Load Screen + REC_LTEX = MKTAG('L','T','E','X'), // Land Texture + REC_LVLC = MKTAG('L','V','L','C'), // Leveled Creature + REC_LVLI = MKTAG('L','V','L','I'), // Leveled Item + REC_LVLN = MKTAG('L','V','L','N'), // Leveled Actor + REC_LVSP = MKTAG('L','V','S','P'), // Leveled Spell + REC_MATO = MKTAG('M','A','T','O'), // Material Object + REC_MATT = MKTAG('M','A','T','T'), // Material Type + REC_MESG = MKTAG('M','E','S','G'), // Message + REC_MGEF = MKTAG('M','G','E','F'), // Magic Effect + REC_MISC = MKTAG('M','I','S','C'), // Misc. Object + REC_MOVT = MKTAG('M','O','V','T'), // Movement Type + REC_MSTT = MKTAG('M','S','T','T'), // Movable Static + REC_MUSC = MKTAG('M','U','S','C'), // Music Type + REC_MUST = MKTAG('M','U','S','T'), // Music Track + REC_NAVI = MKTAG('N','A','V','I'), // Navigation (master data) + REC_NAVM = MKTAG('N','A','V','M'), // Nav Mesh + REC_NPC_ = MKTAG('N','P','C','_'), // Actor (NPC, Creature) + REC_OTFT = MKTAG('O','T','F','T'), // Outfit + REC_PACK = MKTAG('P','A','C','K'), // AI Package + REC_PERK = MKTAG('P','E','R','K'), // Perk + REC_PGRE = MKTAG('P','G','R','E'), // Placed grenade + REC_PHZD = MKTAG('P','H','Z','D'), // Placed hazard + REC_PROJ = MKTAG('P','R','O','J'), // Projectile + REC_QUST = MKTAG('Q','U','S','T'), // Quest + REC_RACE = MKTAG('R','A','C','E'), // Race / Creature type + REC_REFR = MKTAG('R','E','F','R'), // Object Reference + REC_REGN = MKTAG('R','E','G','N'), // Region (Audio/Weather) + REC_RELA = MKTAG('R','E','L','A'), // Relationship + REC_REVB = MKTAG('R','E','V','B'), // Reverb Parameters + REC_RFCT = MKTAG('R','F','C','T'), // Visual Effect + REC_SBSP = MKTAG('S','B','S','P'), // Subspace (TES4 only?) + REC_SCEN = MKTAG('S','C','E','N'), // Scene + REC_SCRL = MKTAG('S','C','R','L'), // Scroll + REC_SGST = MKTAG('S','G','S','T'), // Sigil Stone + REC_SHOU = MKTAG('S','H','O','U'), // Shout + REC_SLGM = MKTAG('S','L','G','M'), // Soul Gem + REC_SMBN = MKTAG('S','M','B','N'), // Story Manager Branch Node + REC_SMEN = MKTAG('S','M','E','N'), // Story Manager Event Node + REC_SMQN = MKTAG('S','M','Q','N'), // Story Manager Quest Node + REC_SNCT = MKTAG('S','N','C','T'), // Sound Category + REC_SNDR = MKTAG('S','N','D','R'), // Sound Reference + REC_SOPM = MKTAG('S','O','P','M'), // Sound Output Model + REC_SOUN = MKTAG('S','O','U','N'), // Sound + REC_SPEL = MKTAG('S','P','E','L'), // Spell + REC_SPGD = MKTAG('S','P','G','D'), // Shader Particle Geometry + REC_STAT = MKTAG('S','T','A','T'), // Static + REC_TACT = MKTAG('T','A','C','T'), // Talking Activator + REC_TES4 = MKTAG('T','E','S','4'), // Plugin info + REC_TREE = MKTAG('T','R','E','E'), // Tree + REC_TXST = MKTAG('T','X','S','T'), // Texture Set + REC_VTYP = MKTAG('V','T','Y','P'), // Voice Type + REC_WATR = MKTAG('W','A','T','R'), // Water Type + REC_WEAP = MKTAG('W','E','A','P'), // Weapon + REC_WOOP = MKTAG('W','O','O','P'), // Word Of Power + REC_WRLD = MKTAG('W','R','L','D'), // World Space + REC_WTHR = MKTAG('W','T','H','R'), // Weather + REC_ACRE = MKTAG('A','C','R','E'), // Placed Creature (TES4 only?) + REC_PGRD = MKTAG('P','G','R','D'), // Pathgrid (TES4 only?) + REC_ROAD = MKTAG('R','O','A','D') // Road (TES4 only?) + }; + + enum SubRecordTypes + { + SUB_HEDR = MKTAG('H','E','D','R'), + SUB_CNAM = MKTAG('C','N','A','M'), + SUB_SNAM = MKTAG('S','N','A','M'), // TES4 only? + SUB_MAST = MKTAG('M','A','S','T'), + SUB_DATA = MKTAG('D','A','T','A'), + SUB_ONAM = MKTAG('O','N','A','M'), + SUB_INTV = MKTAG('I','N','T','V'), + SUB_INCC = MKTAG('I','N','C','C'), + SUB_OFST = MKTAG('O','F','S','T'), // TES4 only? + SUB_DELE = MKTAG('D','E','L','E'), // TES4 only? + + SUB_DNAM = MKTAG('D','N','A','M'), + SUB_EDID = MKTAG('E','D','I','D'), + SUB_FULL = MKTAG('F','U','L','L'), + SUB_LTMP = MKTAG('L','T','M','P'), + SUB_MHDT = MKTAG('M','H','D','T'), + SUB_MNAM = MKTAG('M','N','A','M'), + SUB_MODL = MKTAG('M','O','D','L'), + SUB_NAM0 = MKTAG('N','A','M','0'), + SUB_NAM2 = MKTAG('N','A','M','2'), + SUB_NAM3 = MKTAG('N','A','M','3'), + SUB_NAM4 = MKTAG('N','A','M','4'), + SUB_NAM9 = MKTAG('N','A','M','9'), + SUB_NAMA = MKTAG('N','A','M','A'), + SUB_PNAM = MKTAG('P','N','A','M'), + SUB_RNAM = MKTAG('R','N','A','M'), + SUB_TNAM = MKTAG('T','N','A','M'), + SUB_UNAM = MKTAG('U','N','A','M'), + SUB_WCTR = MKTAG('W','C','T','R'), + SUB_WNAM = MKTAG('W','N','A','M'), + SUB_XEZN = MKTAG('X','E','Z','N'), + SUB_XLCN = MKTAG('X','L','C','N'), + SUB_XXXX = MKTAG('X','X','X','X'), + SUB_ZNAM = MKTAG('Z','N','A','M'), + SUB_MODT = MKTAG('M','O','D','T'), + SUB_ICON = MKTAG('I','C','O','N'), // TES4 only? + + SUB_NVER = MKTAG('N','V','E','R'), + SUB_NVMI = MKTAG('N','V','M','I'), + SUB_NVPP = MKTAG('N','V','P','P'), + SUB_NVSI = MKTAG('N','V','S','I'), + + SUB_NVNM = MKTAG('N','V','N','M'), + SUB_NNAM = MKTAG('N','N','A','M'), + + SUB_XCLC = MKTAG('X','C','L','C'), + SUB_XCLL = MKTAG('X','C','L','L'), + SUB_TVDT = MKTAG('T','V','D','T'), + SUB_XCGD = MKTAG('X','C','G','D'), + SUB_LNAM = MKTAG('L','N','A','M'), + SUB_XCLW = MKTAG('X','C','L','W'), + SUB_XNAM = MKTAG('X','N','A','M'), + SUB_XCLR = MKTAG('X','C','L','R'), + SUB_XWCS = MKTAG('X','W','C','S'), + SUB_XWCN = MKTAG('X','W','C','N'), + SUB_XWCU = MKTAG('X','W','C','U'), + SUB_XCWT = MKTAG('X','C','W','T'), + SUB_XOWN = MKTAG('X','O','W','N'), + SUB_XILL = MKTAG('X','I','L','L'), + SUB_XWEM = MKTAG('X','W','E','M'), + SUB_XCCM = MKTAG('X','C','C','M'), + SUB_XCAS = MKTAG('X','C','A','S'), + SUB_XCMO = MKTAG('X','C','M','O'), + SUB_XCIM = MKTAG('X','C','I','M'), + SUB_XCMT = MKTAG('X','C','M','T'), // TES4 only? + SUB_XRNK = MKTAG('X','R','N','K'), // TES4 only? + SUB_XGLB = MKTAG('X','G','L','B'), // TES4 only? + + SUB_VNML = MKTAG('V','N','M','L'), + SUB_VHGT = MKTAG('V','H','G','T'), + SUB_VCLR = MKTAG('V','C','L','R'), + SUA_BTXT = MKTAG('B','T','X','T'), + SUB_ATXT = MKTAG('A','T','X','T'), + SUB_VTXT = MKTAG('V','T','X','T'), + SUB_VTEX = MKTAG('V','T','E','X'), + + SUB_HNAM = MKTAG('H','N','A','M'), + SUB_GNAM = MKTAG('G','N','A','M'), + + SUB_RCLR = MKTAG('R','C','L','R'), + SUB_RPLI = MKTAG('R','P','L','I'), + SUB_RPLD = MKTAG('R','P','L','D'), + SUB_RDAT = MKTAG('R','D','A','T'), + SUB_RDMD = MKTAG('R','D','M','D'), // TES4 only? + SUB_RDSD = MKTAG('R','D','S','D'), // TES4 only? + SUB_RDGS = MKTAG('R','D','G','S'), // TES4 only? + SUB_RDMO = MKTAG('R','D','M','O'), + SUB_RDSA = MKTAG('R','D','S','A'), + SUB_RDWT = MKTAG('R','D','W','T'), + SUB_RDOT = MKTAG('R','D','O','T'), + SUB_RDMP = MKTAG('R','D','M','P'), + + SUB_MODB = MKTAG('M','O','D','B'), + SUB_OBND = MKTAG('O','B','N','D'), + SUB_MODS = MKTAG('M','O','D','S'), + + SUB_NAME = MKTAG('N','A','M','E'), + SUB_XMRK = MKTAG('X','M','R','K'), + SUB_FNAM = MKTAG('F','N','A','M'), + SUB_XSCL = MKTAG('X','S','C','L'), + SUB_XTEL = MKTAG('X','T','E','L'), + SUB_XTRG = MKTAG('X','T','R','G'), + SUB_XSED = MKTAG('X','S','E','D'), + SUB_XLOD = MKTAG('X','L','O','D'), + SUB_XPCI = MKTAG('X','P','C','I'), + SUB_XLOC = MKTAG('X','L','O','C'), + SUB_XESP = MKTAG('X','E','S','P'), + SUB_XLCM = MKTAG('X','L','C','M'), + SUB_XRTM = MKTAG('X','R','T','M'), + SUB_XACT = MKTAG('X','A','C','T'), + SUB_XCNT = MKTAG('X','C','N','T'), + SUB_VMAD = MKTAG('V','M','A','D'), + SUB_XPRM = MKTAG('X','P','R','M'), + SUB_XMBO = MKTAG('X','M','B','O'), + SUB_XPOD = MKTAG('X','P','O','D'), + SUB_XRMR = MKTAG('X','R','M','R'), + SUB_INAM = MKTAG('I','N','A','M'), + SUB_SCHR = MKTAG('S','C','H','R'), + SUB_XLRM = MKTAG('X','L','R','M'), + SUB_XRGD = MKTAG('X','R','G','D'), + SUB_XRDS = MKTAG('X','R','D','S'), + SUB_XEMI = MKTAG('X','E','M','I'), + SUB_XLIG = MKTAG('X','L','I','G'), + SUB_XALP = MKTAG('X','A','L','P'), + SUB_XNDP = MKTAG('X','N','D','P'), + SUB_XAPD = MKTAG('X','A','P','D'), + SUB_XAPR = MKTAG('X','A','P','R'), + SUB_XLIB = MKTAG('X','L','I','B'), + SUB_XLKR = MKTAG('X','L','K','R'), + SUB_XLRT = MKTAG('X','L','R','T'), + SUB_XCVL = MKTAG('X','C','V','L'), + SUB_XCVR = MKTAG('X','C','V','R'), + SUB_XCZA = MKTAG('X','C','Z','A'), + SUB_XCZC = MKTAG('X','C','Z','C'), + SUB_XFVC = MKTAG('X','F','V','C'), + SUB_XHTW = MKTAG('X','H','T','W'), + SUB_XIS2 = MKTAG('X','I','S','2'), + SUB_XMBR = MKTAG('X','M','B','R'), + SUB_XCCP = MKTAG('X','C','C','P'), + SUB_XPWR = MKTAG('X','P','W','R'), + SUB_XTRI = MKTAG('X','T','R','I'), + SUB_XATR = MKTAG('X','A','T','R'), + SUB_XPRD = MKTAG('X','P','R','D'), + SUB_XPPA = MKTAG('X','P','P','A'), + SUB_PDTO = MKTAG('P','D','T','O'), + SUB_XLRL = MKTAG('X','L','R','L'), + + SUB_QNAM = MKTAG('Q','N','A','M'), + SUB_COCT = MKTAG('C','O','C','T'), + SUB_COED = MKTAG('C','O','E','D'), + SUB_CNTO = MKTAG('C','N','T','O'), + SUB_SCRI = MKTAG('S','C','R','I'), + + SUB_BNAM = MKTAG('B','N','A','M'), + + SUB_BMDT = MKTAG('B','M','D','T'), + SUB_MOD2 = MKTAG('M','O','D','2'), + SUB_MOD3 = MKTAG('M','O','D','3'), + SUB_MOD4 = MKTAG('M','O','D','4'), + SUB_MO2B = MKTAG('M','O','2','B'), + SUB_MO3B = MKTAG('M','O','3','B'), + SUB_MO4B = MKTAG('M','O','4','B'), + SUB_MO2T = MKTAG('M','O','2','T'), + SUB_MO3T = MKTAG('M','O','3','T'), + SUB_MO4T = MKTAG('M','O','4','T'), + SUB_ANAM = MKTAG('A','N','A','M'), + SUB_ENAM = MKTAG('E','N','A','M'), + SUB_ICO2 = MKTAG('I','C','O','2'), + + SUB_ACBS = MKTAG('A','C','B','S'), + SUB_SPLO = MKTAG('S','P','L','O'), + SUB_AIDT = MKTAG('A','I','D','T'), + SUB_PKID = MKTAG('P','K','I','D'), + SUB_HCLR = MKTAG('H','C','L','R'), + SUB_FGGS = MKTAG('F','G','G','S'), + SUB_FGGA = MKTAG('F','G','G','A'), + SUB_FGTS = MKTAG('F','G','T','S'), + SUB_KFFZ = MKTAG('K','F','F','Z'), + + SUB_PFIG = MKTAG('P','F','I','G'), + SUB_PFPC = MKTAG('P','F','P','C'), + + SUB_XHRS = MKTAG('X','H','R','S'), + SUB_XMRC = MKTAG('X','M','R','C'), + + SUB_SNDD = MKTAG('S','N','D','D'), + SUB_SNDX = MKTAG('S','N','D','X'), + + SUB_DESC = MKTAG('D','E','S','C'), + + SUB_ENIT = MKTAG('E','N','I','T'), + SUB_EFID = MKTAG('E','F','I','D'), + SUB_EFIT = MKTAG('E','F','I','T'), + SUB_SCIT = MKTAG('S','C','I','T'), + + SUB_SOUL = MKTAG('S','O','U','L'), + SUB_SLCP = MKTAG('S','L','C','P'), + + SUB_CSCR = MKTAG('C','S','C','R'), + SUB_CSDI = MKTAG('C','S','D','I'), + SUB_CSDC = MKTAG('C','S','D','C'), + SUB_NIFZ = MKTAG('N','I','F','Z'), + SUB_CSDT = MKTAG('C','S','D','T'), + SUB_NAM1 = MKTAG('N','A','M','1'), + SUB_NIFT = MKTAG('N','I','F','T'), + + SUB_LVLD = MKTAG('L','V','L','D'), + SUB_LVLF = MKTAG('L','V','L','F'), + SUB_LVLO = MKTAG('L','V','L','O'), + + SUB_BODT = MKTAG('B','O','D','T'), + SUB_YNAM = MKTAG('Y','N','A','M'), + SUB_DEST = MKTAG('D','E','S','T'), + SUB_DMDL = MKTAG('D','M','D','L'), + SUB_DMDS = MKTAG('D','M','D','S'), + SUB_DMDT = MKTAG('D','M','D','T'), + SUB_DSTD = MKTAG('D','S','T','D'), + SUB_DSTF = MKTAG('D','S','T','F'), + SUB_KNAM = MKTAG('K','N','A','M'), + SUB_KSIZ = MKTAG('K','S','I','Z'), + SUB_KWDA = MKTAG('K','W','D','A'), + SUB_VNAM = MKTAG('V','N','A','M'), + SUB_SDSC = MKTAG('S','D','S','C'), + SUB_MO2S = MKTAG('M','O','2','S'), + SUB_MO4S = MKTAG('M','O','4','S'), + SUB_BOD2 = MKTAG('B','O','D','2'), + SUB_BAMT = MKTAG('B','A','M','T'), + SUB_BIDS = MKTAG('B','I','D','S'), + SUB_ETYP = MKTAG('E','T','Y','P'), + SUB_BMCT = MKTAG('B','M','C','T'), + SUB_MICO = MKTAG('M','I','C','O'), + SUB_MIC2 = MKTAG('M','I','C','2'), + SUB_EAMT = MKTAG('E','A','M','T'), + SUB_EITM = MKTAG('E','I','T','M'), + + SUB_SCTX = MKTAG('S','C','T','X'), + SUB_XLTW = MKTAG('X','L','T','W'), + SUB_XMBP = MKTAG('X','M','B','P'), + SUB_XOCP = MKTAG('X','O','C','P'), + SUB_XRGB = MKTAG('X','R','G','B'), + SUB_XSPC = MKTAG('X','S','P','C'), + SUB_XTNM = MKTAG('X','T','N','M'), + SUB_ATKR = MKTAG('A','T','K','R'), + SUB_CRIF = MKTAG('C','R','I','F'), + SUB_DOFT = MKTAG('D','O','F','T'), + SUB_DPLT = MKTAG('D','P','L','T'), + SUB_ECOR = MKTAG('E','C','O','R'), + SUB_ATKD = MKTAG('A','T','K','D'), + SUB_ATKE = MKTAG('A','T','K','E'), + SUB_FTST = MKTAG('F','T','S','T'), + SUB_HCLF = MKTAG('H','C','L','F'), + SUB_NAM5 = MKTAG('N','A','M','5'), + SUB_NAM6 = MKTAG('N','A','M','6'), + SUB_NAM7 = MKTAG('N','A','M','7'), + SUB_NAM8 = MKTAG('N','A','M','8'), + SUB_PRKR = MKTAG('P','R','K','R'), + SUB_PRKZ = MKTAG('P','R','K','Z'), + SUB_SOFT = MKTAG('S','O','F','T'), + SUB_SPCT = MKTAG('S','P','C','T'), + SUB_TINC = MKTAG('T','I','N','C'), + SUB_TIAS = MKTAG('T','I','A','S'), + SUB_TINI = MKTAG('T','I','N','I'), + SUB_TINV = MKTAG('T','I','N','V'), + SUB_TPLT = MKTAG('T','P','L','T'), + SUB_VTCK = MKTAG('V','T','C','K'), + SUB_SHRT = MKTAG('S','H','R','T'), + SUB_SPOR = MKTAG('S','P','O','R'), + SUB_XHOR = MKTAG('X','H','O','R'), + SUB_CTDA = MKTAG('C','T','D','A'), + SUB_CRDT = MKTAG('C','R','D','T'), + SUB_FNMK = MKTAG('F','N','M','K'), + SUB_FNPR = MKTAG('F','N','P','R'), + SUB_WBDT = MKTAG('W','B','D','T'), + SUB_QUAL = MKTAG('Q','U','A','L'), + SUB_INDX = MKTAG('I','N','D','X'), + SUB_ATTR = MKTAG('A','T','T','R'), + + SUB_XHLT = MKTAG('X','H','L','T'), // Unofficial Oblivion Patch + SUB_XCHG = MKTAG('X','C','H','G'), // thievery.exp + + SUB_XIBS = MKTAG('X','I','B','S'), // FO3 + SUB_REPL = MKTAG('R','E','P','L'), // FO3 + SUB_BIPL = MKTAG('B','I','P','L'), // FO3 + SUB_MODD = MKTAG('M','O','D','D'), // FO3 + SUB_MOSD = MKTAG('M','O','S','D'), // FO3 + SUB_MO3S = MKTAG('M','O','3','S'), // FO3 + SUB_XCET = MKTAG('X','C','E','T'), // FO3 + SUB_LVLG = MKTAG('L','V','L','G'), // FO3 + SUB_NVCI = MKTAG('N','V','C','I'), // FO3 + SUB_NVVX = MKTAG('N','V','V','X'), // FO3 + SUB_NVTR = MKTAG('N','V','T','R'), // FO3 + SUB_NVCA = MKTAG('N','V','C','A'), // FO3 + SUB_NVDP = MKTAG('N','V','D','P'), // FO3 + SUB_NVGD = MKTAG('N','V','G','D'), // FO3 + SUB_NVEX = MKTAG('N','V','E','X'), // FO3 + SUB_XHLP = MKTAG('X','H','L','P'), // FO3 + SUB_XRDO = MKTAG('X','R','D','O'), // FO3 + SUB_XAMT = MKTAG('X','A','M','T'), // FO3 + SUB_XAMC = MKTAG('X','A','M','C'), // FO3 + SUB_XRAD = MKTAG('X','R','A','D'), // FO3 + SUB_XORD = MKTAG('X','O','R','D'), // FO3 + SUB_XCLP = MKTAG('X','C','L','P'), // FO3 + SUB_SCDA = MKTAG('S','C','D','A'), // FO3 + SUB_SCRO = MKTAG('S','C','R','O'), // FO3 + SUB_IMPS = MKTAG('I','M','P','S'), // FO3 Anchorage + SUB_IMPF = MKTAG('I','M','P','F'), // FO3 Anchorage + + SUB_XATO = MKTAG('X','A','T','O'), // FONV + SUB_DAT2 = MKTAG('D','A','T','2'), // FONV + SUB_RCIL = MKTAG('R','C','I','L'), // FONV + SUB_MMRK = MKTAG('M','M','R','K'), // FONV + SUB_SCRV = MKTAG('S','C','R','V'), // FONV + SUB_SCVR = MKTAG('S','C','V','R'), // FONV + SUB_SLSD = MKTAG('S','L','S','D'), // FONV + SUB_XSRF = MKTAG('X','S','R','F'), // FONV + SUB_XSRD = MKTAG('X','S','R','D'), // FONV + SUB_WMI1 = MKTAG('W','M','I','1'), // FONV + SUB_RDID = MKTAG('R','D','I','D'), // FONV + SUB_RDSB = MKTAG('R','D','S','B'), // FONV + SUB_RDSI = MKTAG('R','D','S','I'), // FONV + SUB_BRUS = MKTAG('B','R','U','S'), // FONV + SUB_VATS = MKTAG('V','A','T','S'), // FONV + SUB_VANM = MKTAG('V','A','N','M'), // FONV + SUB_MWD1 = MKTAG('M','W','D','1'), // FONV + SUB_MWD2 = MKTAG('M','W','D','2'), // FONV + SUB_MWD3 = MKTAG('M','W','D','3'), // FONV + SUB_MWD4 = MKTAG('M','W','D','4'), // FONV + SUB_MWD5 = MKTAG('M','W','D','5'), // FONV + SUB_MWD6 = MKTAG('M','W','D','6'), // FONV + SUB_MWD7 = MKTAG('M','W','D','7'), // FONV + SUB_WMI2 = MKTAG('W','M','I','2'), // FONV + SUB_WMI3 = MKTAG('W','M','I','3'), // FONV + SUB_WMS1 = MKTAG('W','M','S','1'), // FONV + SUB_WMS2 = MKTAG('W','M','S','2'), // FONV + SUB_WNM1 = MKTAG('W','N','M','1'), // FONV + SUB_WNM2 = MKTAG('W','N','M','2'), // FONV + SUB_WNM3 = MKTAG('W','N','M','3'), // FONV + SUB_WNM4 = MKTAG('W','N','M','4'), // FONV + SUB_WNM5 = MKTAG('W','N','M','5'), // FONV + SUB_WNM6 = MKTAG('W','N','M','6'), // FONV + SUB_WNM7 = MKTAG('W','N','M','7'), // FONV + }; + + enum MagicEffectID + { + // Alteration + EFI_BRDN = MKTAG('B','R','D','N'), + EFI_FTHR = MKTAG('F','T','H','R'), + EFI_FISH = MKTAG('F','I','S','H'), + EFI_FRSH = MKTAG('F','R','S','H'), + EFI_OPEN = MKTAG('O','P','N','N'), + EFI_SHLD = MKTAG('S','H','L','D'), + EFI_LISH = MKTAG('L','I','S','H'), + EFI_WABR = MKTAG('W','A','B','R'), + EFI_WAWA = MKTAG('W','A','W','A'), + + // Conjuration + EFI_BABO = MKTAG('B','A','B','O'), // Bound Boots + EFI_BACU = MKTAG('B','A','C','U'), // Bound Cuirass + EFI_BAGA = MKTAG('B','A','G','A'), // Bound Gauntlets + EFI_BAGR = MKTAG('B','A','G','R'), // Bound Greaves + EFI_BAHE = MKTAG('B','A','H','E'), // Bound Helmet + EFI_BASH = MKTAG('B','A','S','H'), // Bound Shield + EFI_BWAX = MKTAG('B','W','A','X'), // Bound Axe + EFI_BWBO = MKTAG('B','W','B','O'), // Bound Bow + EFI_BWDA = MKTAG('B','W','D','A'), // Bound Dagger + EFI_BWMA = MKTAG('B','W','M','A'), // Bound Mace + EFI_BWSW = MKTAG('B','W','S','W'), // Bound Sword + EFI_Z001 = MKTAG('Z','0','0','1'), // Summon Rufio's Ghost + EFI_Z002 = MKTAG('Z','0','0','2'), // Summon Ancestor Guardian + EFI_Z003 = MKTAG('Z','0','0','3'), // Summon Spiderling + EFI_Z005 = MKTAG('Z','0','0','5'), // Summon Bear + EFI_ZCLA = MKTAG('Z','C','L','A'), // Summon Clannfear + EFI_ZDAE = MKTAG('Z','D','A','E'), // Summon Daedroth + EFI_ZDRE = MKTAG('Z','D','R','E'), // Summon Dremora + EFI_ZDRL = MKTAG('Z','D','R','L'), // Summon Dremora Lord + EFI_ZFIA = MKTAG('Z','F','I','A'), // Summon Flame Atronach + EFI_ZFRA = MKTAG('Z','F','R','A'), // Summon Frost Atronach + EFI_ZGHO = MKTAG('Z','G','H','O'), // Summon Ghost + EFI_ZHDZ = MKTAG('Z','H','D','Z'), // Summon Headless Zombie + EFI_ZLIC = MKTAG('Z','L','I','C'), // Summon Lich + EFI_ZSCA = MKTAG('Z','S','C','A'), // Summon Scamp + EFI_ZSKE = MKTAG('Z','S','K','E'), // Summon Skeleton + EFI_ZSKA = MKTAG('Z','S','K','A'), // Summon Skeleton Guardian + EFI_ZSKH = MKTAG('Z','S','K','H'), // Summon Skeleton Hero + EFI_ZSKC = MKTAG('Z','S','K','C'), // Summon Skeleton Champion + EFI_ZSPD = MKTAG('Z','S','P','D'), // Summon Spider Daedra + EFI_ZSTA = MKTAG('Z','S','T','A'), // Summon Storm Atronach + EFI_ZWRA = MKTAG('Z','W','R','A'), // Summon Faded Wraith + EFI_ZWRL = MKTAG('Z','W','R','L'), // Summon Gloom Wraith + EFI_ZXIV = MKTAG('Z','X','I','V'), // Summon Xivilai + EFI_ZZOM = MKTAG('Z','Z','O','M'), // Summon Zombie + EFI_TURN = MKTAG('T','U','R','N'), // Turn Undead + + // Destruction + EFI_DGAT = MKTAG('D','G','A','T'), // Damage Attribute + EFI_DGFA = MKTAG('D','G','F','A'), // Damage Fatigue + EFI_DGHE = MKTAG('D','G','H','E'), // Damage Health + EFI_DGSP = MKTAG('D','G','S','P'), // Damage Magicka + EFI_DIAR = MKTAG('D','I','A','R'), // Disintegrate Armor + EFI_DIWE = MKTAG('D','I','W','E'), // Disintegrate Weapon + EFI_DRAT = MKTAG('D','R','A','T'), // Drain Attribute + EFI_DRFA = MKTAG('D','R','F','A'), // Drain Fatigue + EFI_DRHE = MKTAG('D','R','H','E'), // Drain Health + EFI_DRSP = MKTAG('D','R','S','P'), // Drain Magicka + EFI_DRSK = MKTAG('D','R','S','K'), // Drain Skill + EFI_FIDG = MKTAG('F','I','D','G'), // Fire Damage + EFI_FRDG = MKTAG('F','R','D','G'), // Frost Damage + EFI_SHDG = MKTAG('S','H','D','G'), // Shock Damage + EFI_WKDI = MKTAG('W','K','D','I'), // Weakness to Disease + EFI_WKFI = MKTAG('W','K','F','I'), // Weakness to Fire + EFI_WKFR = MKTAG('W','K','F','R'), // Weakness to Frost + EFI_WKMA = MKTAG('W','K','M','A'), // Weakness to Magic + EFI_WKNW = MKTAG('W','K','N','W'), // Weakness to Normal Weapons + EFI_WKPO = MKTAG('W','K','P','O'), // Weakness to Poison + EFI_WKSH = MKTAG('W','K','S','H'), // Weakness to Shock + + // Illusion + EFI_CALM = MKTAG('C','A','L','M'), // Calm + EFI_CHML = MKTAG('C','H','M','L'), // Chameleon + EFI_CHRM = MKTAG('C','H','R','M'), // Charm + EFI_COCR = MKTAG('C','O','C','R'), // Command Creature + EFI_COHU = MKTAG('C','O','H','U'), // Command Humanoid + EFI_DEMO = MKTAG('D','E','M','O'), // Demoralize + EFI_FRNZ = MKTAG('F','R','N','Z'), // Frenzy + EFI_INVI = MKTAG('I','N','V','I'), // Invisibility + EFI_LGHT = MKTAG('L','G','H','T'), // Light + EFI_NEYE = MKTAG('N','E','Y','E'), // Night-Eye + EFI_PARA = MKTAG('P','A','R','A'), // Paralyze + EFI_RALY = MKTAG('R','A','L','Y'), // Rally + EFI_SLNC = MKTAG('S','L','N','C'), // Silence + + // Mysticism + EFI_DTCT = MKTAG('D','T','C','T'), // Detect Life + EFI_DSPL = MKTAG('D','S','P','L'), // Dispel + EFI_REDG = MKTAG('R','E','D','G'), // Reflect Damage + EFI_RFLC = MKTAG('R','F','L','C'), // Reflect Spell + EFI_STRP = MKTAG('S','T','R','P'), // Soul Trap + EFI_SABS = MKTAG('S','A','B','S'), // Spell Absorption + EFI_TELE = MKTAG('T','E','L','E'), // Telekinesis + + // Restoration + EFI_ABAT = MKTAG('A','B','A','T'), // Absorb Attribute + EFI_ABFA = MKTAG('A','B','F','A'), // Absorb Fatigue + EFI_ABHe = MKTAG('A','B','H','e'), // Absorb Health + EFI_ABSP = MKTAG('A','B','S','P'), // Absorb Magicka + EFI_ABSK = MKTAG('A','B','S','K'), // Absorb Skill + EFI_1400 = MKTAG('1','4','0','0'), // Cure Disease + EFI_CUPA = MKTAG('C','U','P','A'), // Cure Paralysis + EFI_CUPO = MKTAG('C','U','P','O'), // Cure Poison + EFI_FOAT = MKTAG('F','O','A','T'), // Fortify Attribute + EFI_FOFA = MKTAG('F','O','F','A'), // Fortify Fatigue + EFI_FOHE = MKTAG('F','O','H','E'), // Fortify Health + EFI_FOSP = MKTAG('F','O','S','P'), // Fortify Magicka + EFI_FOSK = MKTAG('F','O','S','K'), // Fortify Skill + EFI_RSDI = MKTAG('R','S','D','I'), // Resist Disease + EFI_RSFI = MKTAG('R','S','F','I'), // Resist Fire + EFI_RSFR = MKTAG('R','S','F','R'), // Resist Frost + EFI_RSMA = MKTAG('R','S','M','A'), // Resist Magic + EFI_RSNW = MKTAG('R','S','N','W'), // Resist Normal Weapons + EFI_RSPA = MKTAG('R','S','P','A'), // Resist Paralysis + EFI_RSPO = MKTAG('R','S','P','O'), // Resist Poison + EFI_RSSH = MKTAG('R','S','S','H'), // Resist Shock + EFI_REAT = MKTAG('R','E','A','T'), // Restore Attribute + EFI_REFA = MKTAG('R','E','F','A'), // Restore Fatigue + EFI_REHE = MKTAG('R','E','H','E'), // Restore Health + EFI_RESP = MKTAG('R','E','S','P'), // Restore Magicka + + // Effects + EFI_LOCK = MKTAG('L','O','C','K'), // Lock Lock + EFI_SEFF = MKTAG('S','E','F','F'), // Script Effect + EFI_Z020 = MKTAG('Z','0','2','0'), // Summon 20 Extra + EFI_MYHL = MKTAG('M','Y','H','L'), // Summon Mythic Dawn Helmet + EFI_MYTH = MKTAG('M','Y','T','H'), // Summon Mythic Dawn Armor + EFI_REAN = MKTAG('R','E','A','N'), // Reanimate + EFI_DISE = MKTAG('D','I','S','E'), // Disease Info + EFI_POSN = MKTAG('P','O','S','N'), // Poison Info + EFI_DUMY = MKTAG('D','U','M','Y'), // Mehrunes Dagon Custom Effect + EFI_STMA = MKTAG('S','T','M','A'), // Stunted Magicka + EFI_SUDG = MKTAG('S','U','D','G'), // Sun Damage + EFI_VAMP = MKTAG('V','A','M','P'), // Vampirism + EFI_DARK = MKTAG('D','A','R','K'), // Darkness + EFI_RSWD = MKTAG('R','S','W','D') // Resist Water Damage + }; + + // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Groups + enum GroupType + { + Grp_RecordType = 0, + Grp_WorldChild = 1, + Grp_InteriorCell = 2, + Grp_InteriorSubCell = 3, + Grp_ExteriorCell = 4, + Grp_ExteriorSubCell = 5, + Grp_CellChild = 6, + Grp_TopicChild = 7, + Grp_CellPersistentChild = 8, + Grp_CellTemporaryChild = 9, + Grp_CellVisibleDistChild = 10 + }; + + // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Records + enum RecordFlag + { + Rec_ESM = 0x00000001, // (TES4 record only) Master (ESM) file. + Rec_Deleted = 0x00000020, // Deleted + Rec_Constant = 0x00000040, // Constant + Rec_HiddenLMap = 0x00000040, // (REFR) Hidden From Local Map (Needs Confirmation: Related to shields) + Rec_Localized = 0x00000080, // (TES4 record only) Is localized. This will make Skyrim load the + // .STRINGS, .DLSTRINGS, and .ILSTRINGS files associated with the mod. + // If this flag is not set, lstrings are treated as zstrings. + Rec_FireOff = 0x00000080, // (PHZD) Turn off fire + Rec_UpdateAnim = 0x00000100, // Must Update Anims + Rec_NoAccess = 0x00000100, // (REFR) Inaccessible + Rec_Hidden = 0x00000200, // (REFR) Hidden from local map + Rec_StartDead = 0x00000200, // (ACHR) Starts dead /(REFR) MotionBlurCastsShadows + Rec_Persistent = 0x00000400, // Quest item / Persistent reference + Rec_DispMenu = 0x00000400, // (LSCR) Displays in Main Menu + Rec_Disabled = 0x00000800, // Initially disabled + Rec_Ignored = 0x00001000, // Ignored + Rec_DistVis = 0x00008000, // Visible when distant + Rec_RandAnim = 0x00010000, // (ACTI) Random Animation Start + Rec_Danger = 0x00020000, // (ACTI) Dangerous / Off limits (Interior cell) + // Dangerous Can't be set withough Ignore Object Interaction + Rec_Compressed = 0x00040000, // Data is compressed + Rec_CanNotWait = 0x00080000, // Can't wait + Rec_IgnoreObj = 0x00100000, // (ACTI) Ignore Object Interaction + // Ignore Object Interaction Sets Dangerous Automatically + Rec_Marker = 0x00800000, // Is Marker + Rec_Obstacle = 0x02000000, // (ACTI) Obstacle / (REFR) No AI Acquire + Rec_NavMFilter = 0x04000000, // NavMesh Gen - Filter + Rec_NavMBBox = 0x08000000, // NavMesh Gen - Bounding Box + Rec_ExitToTalk = 0x10000000, // (FURN) Must Exit to Talk + Rec_Refected = 0x10000000, // (REFR) Reflected By Auto Water + Rec_ChildUse = 0x20000000, // (FURN/IDLM) Child Can Use + Rec_NoHavok = 0x20000000, // (REFR) Don't Havok Settle + Rec_NavMGround = 0x40000000, // NavMesh Gen - Ground + Rec_NoRespawn = 0x40000000, // (REFR) NoRespawn + Rec_MultiBound = 0x80000000 // (REFR) MultiBound + }; + + typedef std::uint32_t FormId; + +#pragma pack(push, 1) + // NOTE: the label field of a group is not reliable (http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format) + union GroupLabel + { + std::uint32_t value; // formId, blockNo or raw int representation of type + char recordType[4]; // record type in ascii + std::int16_t grid[2]; // grid y, x (note the reverse order) + }; + + union TypeId + { + std::uint32_t value; + char name[4]; // record type in ascii + }; + + struct GroupTypeHeader + { + std::uint32_t typeId; + std::uint32_t groupSize; // includes the 24 bytes (20 for TES4) of header (i.e. this struct) + GroupLabel label; // format based on type + std::int32_t type; + std::uint16_t stamp; // & 0xff for day, & 0xff00 for months since Dec 2002 (i.e. 1 = Jan 2003) + std::uint16_t unknown; + std::uint16_t version; // not in TES4 + std::uint16_t unknown2; // not in TES4 + }; + + struct RecordTypeHeader + { + std::uint32_t typeId; + std::uint32_t dataSize; // does *not* include 24 bytes (20 for TES4) of header + std::uint32_t flags; + FormId id; + std::uint32_t revision; + std::uint16_t version; // not in TES4 + std::uint16_t unknown; // not in TES4 + }; + + union RecordHeader + { + struct GroupTypeHeader group; + struct RecordTypeHeader record; + }; + + struct SubRecordHeader + { + std::uint32_t typeId; + std::uint16_t dataSize; + }; + + // Grid, CellGrid and Vertex are shared by NVMI(NAVI) and NVNM(NAVM) + + struct Grid + { + std::int16_t x; + std::int16_t y; + }; + + union CellGrid + { + FormId cellId; + Grid grid; + }; + + struct Vector3 + { + float x; + float y; + float z; + }; + + typedef Vector3 Vertex; + + // REFR, ACHR, ACRE + struct Position + { + Vector3 pos; + Vector3 rot; // angles are in radian, rz applied first and rx applied last + }; + + // REFR, ACHR, ACRE + struct EnableParent + { + FormId parent; + std::uint32_t flags; //0x0001 = Set Enable State Opposite Parent, 0x0002 = Pop In + }; + + // LVLC, LVLI + struct LVLO + { + std::int16_t level; + std::uint16_t unknown; // sometimes missing + FormId item; + std::int16_t count; + std::uint16_t unknown2; // sometimes missing + }; + + struct InventoryItem // NPC_, CREA, CONT + { + FormId item; + std::uint32_t count; + }; + + struct AIData // NPC_, CREA + { + std::uint8_t aggression; + std::uint8_t confidence; + std::uint8_t energyLevel; + std::uint8_t responsibility; + std::uint32_t aiFlags; + std::uint8_t trainSkill; + std::uint8_t trainLevel; + std::uint16_t unknown; + }; + + struct AttributeValues + { + std::uint8_t strength; + std::uint8_t intelligence; + std::uint8_t willpower; + std::uint8_t agility; + std::uint8_t speed; + std::uint8_t endurance; + std::uint8_t personality; + std::uint8_t luck; + }; + + struct ActorBaseConfig + { +#if 0 + enum ACBS_NPC + { + ACBS_Female = 0x000001, + ACBS_Essential = 0x000002, + ACBS_Respawn = 0x000008, + ACBS_Autocalcstats = 0x000010, + ACBS_PCLevelOffset = 0x000080, + ACBS_NoLowLevelProcessing = 0x000200, + ACBS_NoRumors = 0x002000, + ACBS_Summonable = 0x004000, + ACBS_NoPersuasion = 0x008000, // different meaning to crea + ACBS_CanCorpseCheck = 0x100000 // opposite of crea + }; + + enum ACBS_CREA + { + ACBS_Essential = 0x000002, + ACBS_WeapAndShield = 0x000004, + ACBS_Respawn = 0x000008, + ACBS_PCLevelOffset = 0x000080, + ACBS_NoLowLevelProcessing = 0x000200, + ACBS_NoHead = 0x008000, // different meaning to npc_ + ACBS_NoRightArm = 0x010000, + ACBS_NoLeftArm = 0x020000, + ACBS_NoCombatWater = 0x040000, + ACBS_NoShadow = 0x080000, + ACBS_NoCorpseCheck = 0x100000 // opposite of npc_ + }; +#endif + std::uint32_t flags; + std::uint16_t baseSpell; // Base spell points + std::uint16_t fatigue; // Fatigue + std::uint16_t barterGold; // Barter gold + std::int16_t level; // Level/Offset level + std::uint16_t calcMin; // Calc Min + std::uint16_t calcMax; // Calc Max + }; + + struct ActorFaction + { + FormId faction; + std::int8_t rank; + std::uint8_t unknown1; + std::uint8_t unknown2; + std::uint8_t unknown3; + }; + + union EFI_Label + { + std::uint32_t value; + char effect[4]; + }; + + struct ScriptEffect + { + FormId formId; // Script effect (Magic effect must be SEFF) + std::int32_t school; // Magic school. See Magic schools for more information. + EFI_Label visualEffect; // Visual effect name or 0x00000000 if None + std::uint8_t flags; // 0x01 = Hostile + std::uint8_t unknown1; + std::uint8_t unknown2; + std::uint8_t unknown3; + }; +#pragma pack(pop) + + // For pretty printing GroupHeader labels + std::string printLabel(const GroupLabel& label, const std::uint32_t type); + + std::string printName(const std::uint32_t typeId); + + void gridToString(std::int16_t x, std::int16_t y, std::string& str); +} + +#endif // ESM4_COMMON_H diff --git a/extern/esm4/cont.cpp b/extern/esm4/cont.cpp new file mode 100644 index 000000000..ea86837e1 --- /dev/null +++ b/extern/esm4/cont.cpp @@ -0,0 +1,115 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "cont.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Container::Container() : mFormId(0), mFlags(0), mBoundRadius(0.f), mDataFlags(0), mWeight(0.f), + mOpenSound(0), mCloseSound(0), mScript(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Container::~Container() +{ +} + +void ESM4::Container::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("CONT FULL data read error"); + + break; + } + case ESM4::SUB_DATA: + { + reader.get(mDataFlags); + reader.get(mWeight); + break; + } + case ESM4::SUB_CNTO: + { + static InventoryItem inv; // FIXME: use unique_ptr here? + reader.get(inv); + reader.adjustFormId(inv.item); + mInventory.push_back(inv); + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SNAM: reader.getFormId(mOpenSound); break; + case ESM4::SUB_QNAM: reader.getFormId(mCloseSound); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: // TES5 only + case ESM4::SUB_VMAD: // TES5 only + case ESM4::SUB_OBND: // TES5 only + case ESM4::SUB_COCT: // TES5 only + case ESM4::SUB_COED: // TES5 only + case ESM4::SUB_DEST: // FONV + case ESM4::SUB_DSTD: // FONV + case ESM4::SUB_DSTF: // FONV + case ESM4::SUB_DMDL: // FONV + case ESM4::SUB_DMDT: // FONV + case ESM4::SUB_RNAM: // FONV + { + //std::cout << "CONT " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CONT::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Container::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Container::blank() +//{ +//} diff --git a/extern/esm4/cont.hpp b/extern/esm4/cont.hpp new file mode 100644 index 000000000..9639bc9fb --- /dev/null +++ b/extern/esm4/cont.hpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CONT_H +#define ESM4_CONT_H + +#include + +#include "common.hpp" // InventoryItem + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Container + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + unsigned char mDataFlags; + float mWeight; + + FormId mOpenSound; + FormId mCloseSound; + FormId mScript; // TES4 only + + std::vector mInventory; + + Container(); + virtual ~Container(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_CONT_H diff --git a/extern/esm4/crea.cpp b/extern/esm4/crea.cpp new file mode 100644 index 000000000..ec27e769d --- /dev/null +++ b/extern/esm4/crea.cpp @@ -0,0 +1,223 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "crea.hpp" + +#include +#include +#include +#include +#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +ESM4::Creature::Creature() : mFormId(0), mFlags(0), mDeathItem(0), mScript(0), mCombatStyle(0), + mSoundBase(0), mSound(0), mSoundChance(0), mBaseScale(0.f), + mTurningSpeed(0.f), mFootWeight(0.f), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + + mBloodSpray.clear(); + mBloodDecal.clear(); + + mAIData.aggression = 0; + mAIData.confidence = 0; + mAIData.energyLevel = 0; + mAIData.responsibility = 0; + mAIData.aiFlags = 0; + mAIData.trainSkill = 0; + mAIData.trainLevel = 0; + + std::memset(&mData, 0, sizeof(Data)); +} + +ESM4::Creature::~Creature() +{ +} + +void ESM4::Creature::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_CNTO: + { + static InventoryItem inv; // FIXME: use unique_ptr here? + reader.get(inv); + reader.adjustFormId(inv.item); + mInventory.push_back(inv); + break; + } + case ESM4::SUB_SPLO: + { + FormId id; + reader.getFormId(id); + mSpell.push_back(id); + break; + } + case ESM4::SUB_PKID: + { + FormId id; + reader.getFormId(id); + mAIPackages.push_back(id); + break; + } + case ESM4::SUB_SNAM: + { + reader.get(mFaction); + reader.adjustFormId(mFaction.faction); + break; + } + case ESM4::SUB_INAM: reader.getFormId(mDeathItem); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_AIDT: + { + if (subHdr.dataSize == 20) // FO3 + reader.skipSubRecordData(); + else + reader.get(mAIData); // 12 bytes + break; + } + case ESM4::SUB_ACBS: + { + if (subHdr.dataSize == 24) // FO3 + reader.skipSubRecordData(); + else + reader.get(mBaseConfig); + break; + } + case ESM4::SUB_DATA: + { + if (subHdr.dataSize == 17) // FO3 + reader.skipSubRecordData(); + else + reader.get(mData); + break; + } + case ESM4::SUB_ZNAM: reader.getFormId(mCombatStyle); break; + case ESM4::SUB_CSCR: reader.getFormId(mSoundBase); break; + case ESM4::SUB_CSDI: reader.getFormId(mSound); break; + case ESM4::SUB_CSDC: reader.get(mSoundChance); break; + case ESM4::SUB_BNAM: reader.get(mBaseScale); break; + case ESM4::SUB_TNAM: reader.get(mTurningSpeed); break; + case ESM4::SUB_WNAM: reader.get(mFootWeight); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_NAM0: reader.getZString(mBloodSpray); break; + case ESM4::SUB_NAM1: reader.getZString(mBloodDecal); break; + case ESM4::SUB_NIFZ: + { + std::string str; + if (!reader.getZString(str)) + throw std::runtime_error ("CREA NIFZ data read error"); + + std::stringstream ss(str); + std::string file; + while (std::getline(ss, file, '\0')) // split the strings + mNif.push_back(file); + + break; + } + case ESM4::SUB_NIFT: + { + if (subHdr.dataSize != 4) // FIXME: FO3 + { + reader.skipSubRecordData(); + break; + } + + assert(subHdr.dataSize == 4 && "CREA NIFT datasize error"); + std::uint32_t nift; + reader.get(nift); + if (nift) + std::cout << "CREA NIFT " << mFormId << ", non-zero " << nift << std::endl; + break; + } + case ESM4::SUB_KFFZ: + { + std::string str; + if (!reader.getZString(str)) + throw std::runtime_error ("CREA KFFZ data read error"); + + std::stringstream ss(str); + std::string file; + while (std::getline(ss, file, '\0')) // split the strings + mKf.push_back(file); + + break; + } + case ESM4::SUB_MODT: + case ESM4::SUB_RNAM: + case ESM4::SUB_CSDT: + case ESM4::SUB_OBND: // FO3 + case ESM4::SUB_EAMT: // FO3 + case ESM4::SUB_VTCK: // FO3 + case ESM4::SUB_TPLT: // FO3 + case ESM4::SUB_PNAM: // FO3 + case ESM4::SUB_NAM4: // FO3 + case ESM4::SUB_NAM5: // FO3 + case ESM4::SUB_CNAM: // FO3 + case ESM4::SUB_LNAM: // FO3 + case ESM4::SUB_EITM: // FO3 + case ESM4::SUB_DEST: // FO3 + case ESM4::SUB_DSTD: // FO3 + case ESM4::SUB_DSTF: // FO3 + case ESM4::SUB_DMDL: // FO3 + case ESM4::SUB_DMDT: // FO3 + case ESM4::SUB_COED: // FO3 + { + //std::cout << "CREA " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CREA::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Creature::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Creature::blank() +//{ +//} diff --git a/extern/esm4/crea.hpp b/extern/esm4/crea.hpp new file mode 100644 index 000000000..8d0883350 --- /dev/null +++ b/extern/esm4/crea.hpp @@ -0,0 +1,98 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CREA_H +#define ESM4_CREA_H + +#include + +#include "common.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Creature + { +#pragma pack(push, 1) + struct Data + { + std::uint8_t unknown; + std::uint8_t combat; + std::uint8_t magic; + std::uint8_t stealth; + std::uint16_t soul; + std::uint16_t health; + std::uint16_t unknown2; + std::uint16_t damage; + AttributeValues attribs; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + FormId mDeathItem; + std::vector mSpell; + FormId mScript; + + AIData mAIData; + std::vector mAIPackages; + ActorBaseConfig mBaseConfig; + ActorFaction mFaction; + Data mData; + FormId mCombatStyle; + FormId mSoundBase; + FormId mSound; + std::uint8_t mSoundChance; + float mBaseScale; + float mTurningSpeed; + float mFootWeight; + std::string mBloodSpray; + std::string mBloodDecal; + + float mBoundRadius; + std::vector mNif; // NIF filenames, get directory from mModel + std::vector mKf; + + std::vector mInventory; + + Creature(); + virtual ~Creature(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_CREA_H diff --git a/extern/esm4/door.cpp b/extern/esm4/door.cpp new file mode 100644 index 000000000..744320984 --- /dev/null +++ b/extern/esm4/door.cpp @@ -0,0 +1,101 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "door.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Door::Door() : mFormId(0), mFlags(0), mDoorFlags(0), mBoundRadius(0.f), mScript(0), + mOpenSound(0), mCloseSound(0), mLoopSound(0), mRandomTeleport(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Door::~Door() +{ +} + +void ESM4::Door::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("DOOR FULL data read error"); + + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_SNAM: reader.getFormId(mOpenSound); break; + case ESM4::SUB_ANAM: reader.getFormId(mCloseSound); break; + case ESM4::SUB_BNAM: reader.getFormId(mLoopSound); break; + case ESM4::SUB_FNAM: reader.get(mDoorFlags); break; + case ESM4::SUB_TNAM: reader.getFormId(mRandomTeleport); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: + case ESM4::SUB_DEST: // FO3 + case ESM4::SUB_DSTD: // FO3 + case ESM4::SUB_DSTF: // FO3 + case ESM4::SUB_DMDL: // FO3 + case ESM4::SUB_DMDT: // FO3 + { + //std::cout << "DOOR " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::DOOR::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Door::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Door::blank() +//{ +//} diff --git a/extern/esm4/door.hpp b/extern/esm4/door.hpp new file mode 100644 index 000000000..080d7473e --- /dev/null +++ b/extern/esm4/door.hpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_DOOR_H +#define ESM4_DOOR_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Door + { + enum Flags + { + Flag_OblivionGate = 0x01, + Flag_AutomaticDoor = 0x02, + Flag_Hidden = 0x04, + Flag_MinimalUse = 0x08 + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + std::uint8_t mDoorFlags; + FormId mScript; + FormId mOpenSound; + FormId mCloseSound; + FormId mLoopSound; + FormId mRandomTeleport; + + Door(); + virtual ~Door(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_DOOR_H diff --git a/extern/esm4/eyes.cpp b/extern/esm4/eyes.cpp new file mode 100644 index 000000000..f8d896758 --- /dev/null +++ b/extern/esm4/eyes.cpp @@ -0,0 +1,82 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "eyes.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Eyes::Eyes() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mIcon.clear(); + + mData.flags = 0; +} + +ESM4::Eyes::~Eyes() +{ +} + +void ESM4::Eyes::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("EYES FULL data read error"); + + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: reader.get(mData); break; + default: + throw std::runtime_error("ESM4::EYES::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Eyes::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Eyes::blank() +//{ +//} diff --git a/extern/esm4/eyes.hpp b/extern/esm4/eyes.hpp new file mode 100644 index 000000000..ac7f76d06 --- /dev/null +++ b/extern/esm4/eyes.hpp @@ -0,0 +1,67 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_EYES_H +#define ESM4_EYES_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Eyes + { +#pragma pack(push, 1) + struct Data + { + std::uint8_t flags; // 0x01 = playable? + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mIcon; // inventory + + Data mData; + + Eyes(); + virtual ~Eyes(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_EYES_H diff --git a/extern/esm4/flor.cpp b/extern/esm4/flor.cpp new file mode 100644 index 000000000..d9d1d9220 --- /dev/null +++ b/extern/esm4/flor.cpp @@ -0,0 +1,97 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "flor.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Flora::Flora() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScript(0), mIngredient(0), + mSound(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Flora::~Flora() +{ +} + +void ESM4::Flora::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("FLOR FULL data read error"); + + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_PFIG: reader.getFormId(mIngredient); break; + case ESM4::SUB_PFPC: reader.get(mPercentHarvest); break; + case ESM4::SUB_SNAM: reader.getFormId(mSound); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: + case ESM4::SUB_FNAM: + case ESM4::SUB_OBND: + case ESM4::SUB_PNAM: + case ESM4::SUB_RNAM: + case ESM4::SUB_VMAD: + { + //std::cout << "FLOR " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::FLOR::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Flora::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Flora::blank() +//{ +//} diff --git a/extern/esm4/flor.hpp b/extern/esm4/flor.hpp new file mode 100644 index 000000000..849d0c61f --- /dev/null +++ b/extern/esm4/flor.hpp @@ -0,0 +1,77 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_FLOR_H +#define ESM4_FLOR_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Flora + { +#pragma pack(push, 1) + struct Production + { + std::uint8_t spring; + std::uint8_t summer; + std::uint8_t autumn; + std::uint8_t winter; + + Production::Production() : spring(0), summer(0), autumn(0), winter(0) {} + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + FormId mScript; + FormId mIngredient; + FormId mSound; + Production mPercentHarvest; + + Flora(); + virtual ~Flora(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_FLOR_H diff --git a/extern/esm4/formid.cpp b/extern/esm4/formid.cpp new file mode 100644 index 000000000..9195b3767 --- /dev/null +++ b/extern/esm4/formid.cpp @@ -0,0 +1,76 @@ +/* + Copyright (C) 2016 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#include "formid.hpp" + +#include +#include +#include +#include // strtol + +#include + +namespace ESM4 +{ + void formIdToString(FormId formId, std::string& str) + { + char buf[8+1]; + int res = snprintf(buf, 8+1, "%08X", formId); + if (res > 0 && res < 8+1) + str.assign(buf); + else + throw std::runtime_error("Possible buffer overflow while converting formId"); + } + + std::string formIdToString(FormId formId) + { + std::string str; + formIdToString(formId, str); + return str; + } + + bool isFormId(const std::string& str, FormId *id) + { + if (str.size() != 8) + return false; + + char *tmp; + errno = 0; + unsigned long val = strtol(str.c_str(), &tmp, 16); + + if (tmp == str.c_str() || *tmp != '\0' || ((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE)) + return false; + + if (id != nullptr) + *id = static_cast(val); + + return true; + } + + FormId stringToFormId(const std::string& str) + { + if (str.size() != 8) + throw std::out_of_range("StringToFormId: incorrect string size"); + + return static_cast(std::stoul(str, nullptr, 16)); + } +} diff --git a/extern/esm4/formid.hpp b/extern/esm4/formid.hpp new file mode 100644 index 000000000..6bb2c475e --- /dev/null +++ b/extern/esm4/formid.hpp @@ -0,0 +1,42 @@ +/* + Copyright (C) 2016 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#ifndef ESM4_FORMID_H +#define ESM4_FORMID_H + +#include +#include + +namespace ESM4 +{ + typedef std::uint32_t FormId; + + void formIdToString(FormId formId, std::string& str); + + std::string formIdToString(FormId formId); + + bool isFormId(const std::string& str, FormId *id = nullptr); + + FormId stringToFormId(const std::string& str); +} + +#endif // ESM4_FORMID_H diff --git a/extern/esm4/furn.cpp b/extern/esm4/furn.cpp new file mode 100644 index 000000000..157477fa6 --- /dev/null +++ b/extern/esm4/furn.cpp @@ -0,0 +1,106 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "furn.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Furniture::Furniture() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScript(0), + mActiveMarkerFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Furniture::~Furniture() +{ +} + +void ESM4::Furniture::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("FURN FULL data read error"); + + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_MNAM: reader.get(mActiveMarkerFlags); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_DEST: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: + case ESM4::SUB_ENAM: + case ESM4::SUB_FNAM: + case ESM4::SUB_FNMK: + case ESM4::SUB_FNPR: + case ESM4::SUB_KNAM: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_MODS: + case ESM4::SUB_NAM0: + case ESM4::SUB_OBND: + case ESM4::SUB_PNAM: + case ESM4::SUB_VMAD: + case ESM4::SUB_WBDT: + case ESM4::SUB_XMRK: + { + //std::cout << "FURN " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::FURN::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Furniture::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Furniture::blank() +//{ +//} diff --git a/extern/esm4/furn.hpp b/extern/esm4/furn.hpp new file mode 100644 index 000000000..e0ac7e6cf --- /dev/null +++ b/extern/esm4/furn.hpp @@ -0,0 +1,63 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_FURN_H +#define ESM4_FURN_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Furniture + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + FormId mScript; + std::uint32_t mActiveMarkerFlags; + + Furniture(); + virtual ~Furniture(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_FURN_H diff --git a/extern/esm4/gras.cpp b/extern/esm4/gras.cpp new file mode 100644 index 000000000..d095a5ee7 --- /dev/null +++ b/extern/esm4/gras.cpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "gras.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Grass::Grass() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::Grass::~Grass() +{ +} + +void ESM4::Grass::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + { + //std::cout << "GRAS " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::GRAS::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Grass::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Grass::blank() +//{ +//} diff --git a/extern/esm4/gras.hpp b/extern/esm4/gras.hpp new file mode 100644 index 000000000..08e4a5800 --- /dev/null +++ b/extern/esm4/gras.hpp @@ -0,0 +1,97 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_GRAS_H +#define ESM4_GRAS_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Grass + { +#pragma pack(push, 1) + // unused fields are probably packing + struct Data + { + std::uint8_t density; + std::uint8_t minSlope; + std::uint8_t maxSlope; + std::uint8_t unused; + std::uint16_t distanceFromWater; + std::uint16_t unused2; + /* + 1 Above - At Least + 2 Above - At Most + 3 Below - At Least + 4 Below - At Most + 5 Either - At Least + 6 Either - At Most + 7 Either - At Most Above + 8 Either - At Most Below + */ + std::uint32_t waterDistApplication; + float positionRange; + float heightRange; + float colorRange; + float wavePeriod; + /* + 0x01 Vertex Lighting + 0x02 Uniform Scaling + 0x04 Fit to Slope + */ + std::uint8_t flags; + std::uint8_t unused3; + std::uint16_t unused4; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + + Data mData; + + Grass(); + virtual ~Grass(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_GRAS_H diff --git a/extern/esm4/grup.hpp b/extern/esm4/grup.hpp new file mode 100644 index 000000000..345fb11c8 --- /dev/null +++ b/extern/esm4/grup.hpp @@ -0,0 +1,157 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_GRUP_H +#define ESM4_GRUP_H + +#include + +#include "common.hpp" // GroupLabel + +namespace ESM4 +{ + // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format#Hierarchical_Top_Groups + // + // Type | Info | + // ------+--------------------------------------+------------------- + // 2 | Interior Cell Block | + // 3 | Interior Cell Sub-Block | + // R | CELL | + // 6 | Cell Childen | + // 8 | Persistent children | + // R | REFR, ACHR, ACRE | + // 10 | Visible distant children | + // R | REFR, ACHR, ACRE | + // 9 | Temp Children | + // R | PGRD | + // R | REFR, ACHR, ACRE | + // | | + // 0 | Top (Type) | + // R | WRLD | + // 1 | World Children | + // R | ROAD | + // R | CELL | + // 6 | Cell Childen | + // 8 | Persistent children | + // R | REFR, ACHR, ACRE | + // 10 | Visible distant children | + // R | REFR, ACHR, ACRE | + // 9 | Temp Children | + // R | PGRD | + // R | REFR, ACHR, ACRE | + // 4 | Exterior World Block | + // 5 | Exterior World Sub-block | + // R | CELL | + // 6 | Cell Childen | + // 8 | Persistent children | + // R | REFR, ACHR, ACRE | + // 10 | Visible distant children | + // R | REFR, ACHR, ACRE | + // 9 | Temp Children | + // R | LAND | + // R | PGRD | + // R | REFR, ACHR, ACRE | + // + struct WorldGroup + { + FormId mWorld; // WRLD record for this group + + // occurs only after World Child (type 1) + // since GRUP label may not be reliable, need to keep the formid of the current WRLD in + // the reader's context + FormId mRoad; + + std::vector mCells; // FIXME should this be CellGroup* instead? + + WorldGroup() : mWorld(0), mRoad(0) {} + }; + + // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format/CELL + // + // The block and subblock groups for an interior cell are determined by the last two decimal + // digits of the lower 3 bytes of the cell form ID (the modindex is not included in the + // calculation). For example, for form ID 0x000CF2=3314, the block is 4 and the subblock is 1. + // + // The block and subblock groups for an exterior cell are determined by the X-Y coordinates of + // the cell. Each block contains 16 subblocks (4x4) and each subblock contains 64 cells (8x8). + // So each block contains 1024 cells (32x32). + // + // NOTE: There may be many CELL records in one subblock + struct CellGroup + { + FormId mCell; // CELL record for this cell group + int mCellModIndex; // from which file to get the CELL record (e.g. may have been updated) + + // For retrieving parent group size (for lazy loading or skipping) and sub-block number / grid + // NOTE: There can be more than one file that adds/modifies records to this cell group + // + // Use Case 1: To quickly get only the visble when distant records: + // + // - Find the FormId of the CELL (maybe WRLD/X/Y grid lookup or from XTEL of a REFR) + // - search a map of CELL FormId to CellGroup + // - load CELL and its child groups (or load the visible distant only, or whatever) + // + // Use Case 2: Scan the files but don't load CELL or cell group + // + // - Load referenceables and other records up front, updating them as required + // - Don't load CELL, LAND, PGRD or ROAD (keep FormId's and file index, and file + // context then skip the rest of the group) + // + std::vector mHeaders; // FIXME: is this needed? + + // FIXME: should these be pairs? i.e. so that we know from which file + // the formid came (it may have been updated by a mod) + // but does it matter? the record itself keeps track of whether it is base, + // added or modified anyway + // FIXME: should these be maps? e.g. std::map + // or vector for storage with a corresponding map of index? + + // cache (modindex adjusted) formId's of children + // FIXME: also need file index + file context of all those that has type 8 GRUP + GroupTypeHeader mHdrPersist; + std::vector mPersistent; // REFR, ACHR, ACRE + std::vector mdelPersistent; + + // FIXME: also need file index + file context of all those that has type 10 GRUP + GroupTypeHeader mHdrVisDist; + std::vector mVisibleDist; // REFR, ACHR, ACRE + std::vector mdelVisibleDist; + + // FIXME: also need file index + file context of all those that has type 9 GRUP + GroupTypeHeader mHdrTemp; + FormId mLand; // if present, assume only one LAND per exterior CELL + FormId mPgrd; // if present, seems to be the first record after LAND in Temp Cell Child GRUP + std::vector mTemporary; // REFR, ACHR, ACRE + std::vector mdelTemporary; + + // need to keep modindex and context for lazy loading (of all the files that contribute + // to this group) + + CellGroup() : mCell(0), mLand(0), mPgrd(0) {} + }; +} + +#endif // ESM4_GRUP_H diff --git a/extern/esm4/hair.cpp b/extern/esm4/hair.cpp new file mode 100644 index 000000000..73cfa6fbe --- /dev/null +++ b/extern/esm4/hair.cpp @@ -0,0 +1,83 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "hair.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Hair::Hair() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.flags = 0; +} + +ESM4::Hair::~Hair() +{ +} + +void ESM4::Hair::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + { + //std::cout << "HAIR " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::HAIR::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Hair::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Hair::blank() +//{ +//} diff --git a/extern/esm4/hair.hpp b/extern/esm4/hair.hpp new file mode 100644 index 000000000..f8086cbce --- /dev/null +++ b/extern/esm4/hair.hpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_HAIR +#define ESM4_HAIR + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Hair + { +#pragma pack(push, 1) + struct Data + { + std::uint8_t flags; // 0x01 = not playable, 0x02 = not male, 0x04 = not female, ?? = fixed + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + Data mData; + + Hair(); + virtual ~Hair(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_HAIR diff --git a/extern/esm4/idle.cpp b/extern/esm4/idle.cpp new file mode 100644 index 000000000..ed02d8536 --- /dev/null +++ b/extern/esm4/idle.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "idle.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::IdleAnimation::IdleAnimation() : mFormId(0), mFlags(0), mParent(0), mPrevious(0) +{ + mEditorId.clear(); + mCollision.clear(); + mEvent.clear(); +} + +ESM4::IdleAnimation::~IdleAnimation() +{ +} + +void ESM4::IdleAnimation::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_DNAM: reader.getZString(mCollision); break; + case ESM4::SUB_ENAM: reader.getZString(mEvent); break; + case ESM4::SUB_ANAM: + { + reader.get(mParent); + reader.get(mPrevious); + break; + } + case ESM4::SUB_CTDA: // formId + case ESM4::SUB_DATA: // formId + { + //std::cout << "IDLE " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::IDLE::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::IdleAnimation::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::IdleAnimation::blank() +//{ +//} diff --git a/extern/esm4/idle.hpp b/extern/esm4/idle.hpp new file mode 100644 index 000000000..7965332d7 --- /dev/null +++ b/extern/esm4/idle.hpp @@ -0,0 +1,61 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_IDLE_H +#define ESM4_IDLE_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct IdleAnimation + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mCollision; + std::string mEvent; + + FormId mParent; // IDLE or AACT + FormId mPrevious; + + IdleAnimation(); + virtual ~IdleAnimation(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_IDLE_H diff --git a/extern/esm4/ingr.cpp b/extern/esm4/ingr.cpp new file mode 100644 index 000000000..0bd54689f --- /dev/null +++ b/extern/esm4/ingr.cpp @@ -0,0 +1,137 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "ingr.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Ingredient::Ingredient() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScript(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; + mEnchantment.value = 0; + mEnchantment.flags = 0; + + std::memset(&mEffect, 0, sizeof(ScriptEffect)); +} + +ESM4::Ingredient::~Ingredient() +{ +} + +void ESM4::Ingredient::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (mFullName.empty()) + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("INGR FULL data read error"); + + break; + } + else // in TES4 subsequent FULL records are script effect names + { + // FIXME: should be part of a struct? + std::string scriptEffectName; + if (!reader.getZString(scriptEffectName)) + throw std::runtime_error ("INGR FULL data read error"); + + mScriptEffect.push_back(scriptEffectName); + + break; + } + } + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM4::VER_094 || reader.esmVersion() == ESM4::VER_170) + if (subHdr.dataSize == 8) // FO3 is size 4 even though VER_094 + reader.get(mData); + else + reader.get(mData.weight); + + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_ENIT: reader.get(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_SCIT: + { + reader.get(mEffect); + reader.adjustFormId(mEffect.formId); + break; + } + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: // Dragonborn only? + case ESM4::SUB_EFID: + case ESM4::SUB_EFIT: + case ESM4::SUB_OBND: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_VMAD: + case ESM4::SUB_YNAM: + case ESM4::SUB_ZNAM: + case ESM4::SUB_ETYP: // FO3 + { + //std::cout << "INGR " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::INGR::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Ingredient::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Ingredient::blank() +//{ +//} diff --git a/extern/esm4/ingr.hpp b/extern/esm4/ingr.hpp new file mode 100644 index 000000000..6aa86ca81 --- /dev/null +++ b/extern/esm4/ingr.hpp @@ -0,0 +1,82 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_INGR_H +#define ESM4_INGR_H + +#include + +#include "common.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Ingredient + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; + float weight; + }; + + struct ENIT + { + std::uint32_t value; + std::uint32_t flags; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + std::vector mScriptEffect; // FIXME: prob. should be in a struct + FormId mScript; + ScriptEffect mEffect; + ENIT mEnchantment; + + Data mData; + + Ingredient(); + virtual ~Ingredient(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_INGR_H diff --git a/extern/esm4/keym.cpp b/extern/esm4/keym.cpp new file mode 100644 index 000000000..450742389 --- /dev/null +++ b/extern/esm4/keym.cpp @@ -0,0 +1,100 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "keym.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Key::Key() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScript(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::Key::~Key() +{ +} + +void ESM4::Key::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("KEYM FULL data read error"); + + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: + case ESM4::SUB_YNAM: + case ESM4::SUB_ZNAM: + case ESM4::SUB_MICO: // FO3 + { + //std::cout << "KEYM " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::KEYM::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Key::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Key::blank() +//{ +//} diff --git a/extern/esm4/keym.hpp b/extern/esm4/keym.hpp new file mode 100644 index 000000000..18a458a79 --- /dev/null +++ b/extern/esm4/keym.hpp @@ -0,0 +1,73 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_KEYM_H +#define ESM4_KEYM_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Key + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + FormId mScript; + + Data mData; + + Key(); + virtual ~Key(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_KEYM_H diff --git a/extern/esm4/land.cpp b/extern/esm4/land.cpp new file mode 100644 index 000000000..8367d08d5 --- /dev/null +++ b/extern/esm4/land.cpp @@ -0,0 +1,222 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "land.hpp" + +#include +#include + +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +ESM4::Land::Land() : mFormId(0), mFlags(0), mLandFlags(0), mDataTypes(0) +{ + for (int i = 0; i < 4; ++i) + mTextures[i].base.formId = 0; +} + +ESM4::Land::~Land() +{ +} + +// overlap north +// +// 32 +// 31 +// 30 +// overlap . +// west . +// . +// 2 +// 1 +// 0 +// 0 1 2 ... 30 31 32 +// +// overlap south +// +void ESM4::Land::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + TxtLayer layer; + std::int8_t currentAddQuad = -1; // for VTXT following ATXT + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_DATA: + { + reader.get(mLandFlags); + break; + } + case ESM4::SUB_VNML: // vertex normals, 33x33x(1+1+1) = 3267 + { + reader.get(mVertNorm); + mDataTypes |= LAND_VNML; + break; + } + case ESM4::SUB_VHGT: // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 + { +#if 0 + reader.get(mHeightMap.heightOffset); + reader.get(mHeightMap.gradientData); + reader.get(mHeightMap.unknown1); + reader.get(mHeightMap.unknown2); +#endif + reader.get(mHeightMap); + mDataTypes |= LAND_VHGT; + break; + } + case ESM4::SUB_VCLR: // vertex colours, 24bit RGB, 33x33x(1+1+1) = 3267 + { + reader.get(mVertColr); + mDataTypes |= LAND_VCLR; + break; + } + case ESM4::SUA_BTXT: + { + BTXT base; + if (reader.get(base)) + { + assert(base.quadrant < 4 && base.quadrant >= 0 && "base texture quadrant index error"); + + reader.adjustFormId(base.formId); + mTextures[base.quadrant].base = base; // FIXME: any way to avoid double-copying? +#if 0 + std::cout << "Base Texture formid: 0x" + << std::hex << mTextures[base.quadrant].base.formId + << ", quad " << std::dec << (int)base.quadrant << std::endl; +#endif + } + break; + } + case ESM4::SUB_ATXT: + { + if (currentAddQuad != -1) + { + // FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now + //std::cout << "ESM4::Land VTXT empty layer " << (int)layer.additional.layer << std::endl; + mTextures[currentAddQuad].layers.push_back(layer); + } + reader.get(layer.additional); + reader.adjustFormId(layer.additional.formId); + assert(layer.additional.quadrant < 4 && layer.additional.quadrant >= 0 + && "additional texture quadrant index error"); +#if 0 + std::cout << "Additional Texture formId: 0x" + << std::hex << layer.additional.formId + << ", quad " << std::dec << (int)layer.additional.quadrant << std::endl; + std::cout << "Additional Texture layer: " + << std::dec << (int)layer.additional.layer << std::endl; +#endif + currentAddQuad = layer.additional.quadrant; + break; + } + case ESM4::SUB_VTXT: + { + assert(currentAddQuad != -1 && "VTXT without ATXT found"); + + int count = (int)reader.subRecordHeader().dataSize / sizeof(ESM4::Land::VTXT); + int remainder = reader.subRecordHeader().dataSize % sizeof(ESM4::Land::VTXT); + assert(remainder == 0 && "ESM4::LAND VTXT data size error"); + + if (count) + { + layer.data.resize(count); + std::vector::iterator it = layer.data.begin(); + for (;it != layer.data.end(); ++it) + { + reader.get(*it); + // FIXME: debug only + //std::cout << "pos: " << std::dec << (int)(*it).position << std::endl; + } + } + mTextures[currentAddQuad].layers.push_back(layer); + + // Assumed that the layers are added in the correct sequence + // FIXME: Knights.esp doesn't seem to observe this - investigate more + //assert(layer.additional.layer == mTextures[currentAddQuad].layers.size()-1 + //&& "additional texture layer index error"); + + currentAddQuad = -1; + // FIXME: debug only + //std::cout << "VTXT: count " << std::dec << count << std::endl; + break; + } + case ESM4::SUB_VTEX: // only in Oblivion? + { + int count = (int)reader.subRecordHeader().dataSize / sizeof(FormId); + int remainder = reader.subRecordHeader().dataSize % sizeof(FormId); + assert(remainder == 0 && "ESM4::LAND VTEX data size error"); + + if (count) + { + mIds.resize(count); + for (std::vector::iterator it = mIds.begin(); it != mIds.end(); ++it) + { + reader.getFormId(*it); + // FIXME: debug only + //std::cout << "VTEX: " << std::hex << *it << std::endl; + } + } + break; + } + default: + throw std::runtime_error("ESM4::LAND::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } + + bool missing = false; + for (int i = 0; i < 4; ++i) + { + if (mTextures[i].base.formId == 0) + { + //std::cout << "ESM::LAND " << ESM4::formIdToString(mFormId) << " missing base, quad " << i << std::endl; + missing = true; + } + } + // at least one of the quadrants do not have a base texture, return without setting the flag + if (!missing) + mDataTypes |= LAND_VTEX; +} + +//void ESM4::Land::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Land::blank() +//{ +//} diff --git a/extern/esm4/land.hpp b/extern/esm4/land.hpp new file mode 100644 index 000000000..c306eb87b --- /dev/null +++ b/extern/esm4/land.hpp @@ -0,0 +1,134 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LAND_H +#define ESM4_LAND_H + +#include +#include +#include + +namespace ESM4 +{ + class Reader; + typedef std::uint32_t FormId; + + struct Land + { + enum + { + LAND_VNML = 1, + LAND_VHGT = 2, + LAND_WNAM = 4, // only in TES3? + LAND_VCLR = 8, + LAND_VTEX = 16 + }; + + // number of vertices per side + static const int VERTS_PER_SIDE = 33; + + // cell terrain size in world coords + static const int REAL_SIZE = 4096; + + // total number of vertices + static const int LAND_NUM_VERTS = VERTS_PER_SIDE * VERTS_PER_SIDE; + + static const int HEIGHT_SCALE = 8; + + //number of textures per side of a land quadrant + static const int QUAD_TEXTURE_PER_SIDE = 16; + +#pragma pack(push,1) + struct VHGT + { + float heightOffset; + std::int8_t gradientData[VERTS_PER_SIDE * VERTS_PER_SIDE]; + std::uint16_t unknown1; + unsigned char unknown2; + }; + + struct BTXT + { + FormId formId; + std::uint8_t quadrant; // 0 = bottom left. 1 = bottom right. 2 = upper-left. 3 = upper-right + std::uint8_t unknown1; + std::uint16_t unknown2; + }; + + struct ATXT + { + FormId formId; + std::uint8_t quadrant; // 0 = bottom left. 1 = bottom right. 2 = upper-left. 3 = upper-right + std::uint8_t unknown; + std::uint16_t layer; // texture layer, 0..7 + }; + + struct VTXT + { + std::uint16_t position; // 0..288 (17x17 grid) + std::uint8_t unknown1; + std::uint8_t unknown2; + float opacity; + }; +#pragma pack(pop) + + struct TxtLayer + { + ATXT additional; + std::vector data; // FIXME: is this UV map? + }; + + struct Texture + { + BTXT base; + std::vector layers; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::uint32_t mLandFlags; // from DATA subrecord + + // FIXME: lazy loading not yet implemented + int mDataTypes; // which data types are loaded + + signed char mVertNorm[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VNML subrecord + signed char mVertColr[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VCLR subrecord + VHGT mHeightMap; + Texture mTextures[4]; // 0 = bottom left. 1 = bottom right. 2 = upper-left. 3 = upper-right + std::vector mIds; // land texture (LTEX) formids + + Land(); + virtual ~Land(); + + virtual void load(Reader& reader); + //virtual void save(Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LAND_H diff --git a/extern/esm4/ligh.cpp b/extern/esm4/ligh.cpp new file mode 100644 index 000000000..a6c7882ce --- /dev/null +++ b/extern/esm4/ligh.cpp @@ -0,0 +1,127 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "ligh.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Light::Light() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScript(0), mSound(0), + mFade(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); +} + +ESM4::Light::~Light() +{ +} + +void ESM4::Light::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM4::VER_132 || esmVer == ESM4::VER_133 || esmVer == ESM4::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("LIGH FULL data read error"); + + break; + } + case ESM4::SUB_DATA: + { + if (isFONV || (esmVer == ESM4::VER_094 && subHdr.dataSize == 32)/*FO3*/) + { + reader.get(mData.time); // uint32 + } + else + reader.get(mData.duration); // float + + reader.get(mData.radius); + reader.get(mData.colour); + reader.get(mData.flags); + //if (reader.esmVersion() == ESM4::VER_094 || reader.esmVersion() == ESM4::VER_170) + if (subHdr.dataSize == 48) + { + reader.get(mData.falloff); + reader.get(mData.FOV); + reader.get(mData.nearClip); + reader.get(mData.frequency); + reader.get(mData.intensityAmplitude); + reader.get(mData.movementAmplitude); + } + else if (subHdr.dataSize == 32) + { + reader.get(mData.falloff); + reader.get(mData.FOV); + } + reader.get(mData.value); + reader.get(mData.weight); + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_SNAM: reader.getFormId(mSound); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_FNAM: reader.get(mFade); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: // Dragonborn only? + { + //std::cout << "LIGH " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LIGH::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Light::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Light::blank() +//{ +//} diff --git a/extern/esm4/ligh.hpp b/extern/esm4/ligh.hpp new file mode 100644 index 000000000..042c07eb1 --- /dev/null +++ b/extern/esm4/ligh.hpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LIGH_H +#define ESM4_LIGH_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Light + { + struct Data + { + std::uint32_t time; // FO/FONV only + float duration; + std::uint32_t radius; + std::uint32_t colour; // RGBA + std::int32_t flags; + float falloff; + float FOV; + float nearClip; // TES5 only + float frequency; // TES5 only + float intensityAmplitude; // TES5 only + float movementAmplitude; // TES5 only + std::uint32_t value; // gold + float weight; + Data() : duration(-1), radius(0), flags(0), colour(0), falloff(1.f), FOV(90), + nearClip(0.f), frequency(0.f), intensityAmplitude(0.f), movementAmplitude(0.f), + value(0), weight(0.f) // FIXME: FOV in degrees or radians? + {} + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; + + float mBoundRadius; + + FormId mScript; + FormId mSound; + + float mFade; + + Data mData; + + Light(); + virtual ~Light(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LIGH_H diff --git a/extern/esm4/ltex.cpp b/extern/esm4/ltex.cpp new file mode 100644 index 000000000..2e5e9cac0 --- /dev/null +++ b/extern/esm4/ltex.cpp @@ -0,0 +1,106 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "ltex.hpp" + +#include +#include + +#include "reader.hpp" +//#include "writer.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +ESM4::LandTexture::LandTexture() : mFormId(0), mFlags(0), mHavokFriction(0), mHavokRestitution(0), + mTextureSpecular(0), mGrass(0), mHavokMaterial(0), mTexture(0), + mMaterial(0) +{ + mEditorId.clear(); + mTextureFile.clear(); +} + +ESM4::LandTexture::~LandTexture() +{ +} + +void ESM4::LandTexture::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM4::VER_132 || esmVer == ESM4::VER_133 || esmVer == ESM4::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_HNAM: + { + if (isFONV) + { + reader.skipSubRecordData(); // FIXME: skip FONV for now + break; + } + + if ((reader.esmVersion() == ESM4::VER_094 || reader.esmVersion() == ESM4::VER_170) + && subHdr.dataSize == 2) // FO3 is VER_094 but dataSize 3 + { + //assert(subHdr.dataSize == 2 && "LTEX unexpected HNAM size"); + reader.get(mHavokFriction); + reader.get(mHavokRestitution); + } + else + { + assert(subHdr.dataSize == 3 && "LTEX unexpected HNAM size"); + reader.get(mHavokMaterial); + reader.get(mHavokFriction); + reader.get(mHavokRestitution); + } + break; + } + case ESM4::SUB_ICON: reader.getZString(mTextureFile); break; // Oblivion only? + case ESM4::SUB_SNAM: reader.get(mTextureSpecular); break; + case ESM4::SUB_GNAM: reader.getFormId(mGrass); break; + case ESM4::SUB_TNAM: reader.getFormId(mTexture); break; // TES5 only + case ESM4::SUB_MNAM: reader.getFormId(mMaterial); break; // TES5 only + default: + throw std::runtime_error("ESM4::LTEX::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::LandTexture::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LandTexture::blank() +//{ +//} diff --git a/extern/esm4/ltex.hpp b/extern/esm4/ltex.hpp new file mode 100644 index 000000000..d830ddb41 --- /dev/null +++ b/extern/esm4/ltex.hpp @@ -0,0 +1,74 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LTEX_H +#define ESM4_LTEX_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct LandTexture + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::uint8_t mHavokFriction; + std::uint8_t mHavokRestitution; + + std::uint8_t mTextureSpecular; // default 30 + FormId mGrass; + + // ------ TES4 only ----- + + std::string mTextureFile; + std::uint8_t mHavokMaterial; + + // ------ TES5 only ----- + + FormId mTexture; + FormId mMaterial; + + // ---------------------- + + LandTexture(); + virtual ~LandTexture(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LTEX_H diff --git a/extern/esm4/lvlc.cpp b/extern/esm4/lvlc.cpp new file mode 100644 index 000000000..dc2e71f88 --- /dev/null +++ b/extern/esm4/lvlc.cpp @@ -0,0 +1,105 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "lvlc.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LeveledCreature::LeveledCreature() : mFormId(0), mFlags(0), mScript(0), mTemplate(0), + mChanceNone(0), mLvlCreaFlags(0) +{ + mEditorId.clear(); +} + +ESM4::LeveledCreature::~LeveledCreature() +{ +} + +void ESM4::LeveledCreature::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_TNAM: reader.getFormId(mTemplate); break; + case ESM4::SUB_LVLD: reader.get(mChanceNone); break; + case ESM4::SUB_LVLF: reader.get(mLvlCreaFlags); break; + case ESM4::SUB_LVLO: + { + static LVLO lvlo; + if (subHdr.dataSize != 12) + { + if (subHdr.dataSize == 8) + { + reader.get(lvlo.level); + reader.get(lvlo.item); + reader.get(lvlo.count); + //std::cout << "LVLC " << mEditorId << " LVLO lev " << lvlo.level << ", item " << lvlo.item + //<< ", count " << lvlo.count << std::endl; + // FIXME: seems to happen only once, don't add to mLvlObject + // LVLC TesKvatchCreature LVLO lev 1, item 1393819648, count 2 + // 0x0001, 0x5314 0000, 0x0002 + break; + } + else + throw std::runtime_error("ESM4::LVLC::load - " + mEditorId + " LVLO size error"); + } + else + reader.get(lvlo); + + reader.adjustFormId(lvlo.item); + mLvlObject.push_back(lvlo); + break; + } + case ESM4::SUB_OBND: // FO3 + { + //std::cout << "LVLC " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LVLC::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::LeveledCreature::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LeveledCreature::blank() +//{ +//} diff --git a/extern/esm4/lvlc.hpp b/extern/esm4/lvlc.hpp new file mode 100644 index 000000000..4fa1bc0e6 --- /dev/null +++ b/extern/esm4/lvlc.hpp @@ -0,0 +1,62 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LVLC_H +#define ESM4_LVLC_H + +#include + +#include "common.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct LeveledCreature + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + FormId mScript; + FormId mTemplate; + std::int8_t mChanceNone; + std::uint8_t mLvlCreaFlags; + std::vector mLvlObject; + + LeveledCreature(); + virtual ~LeveledCreature(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LVLC_H diff --git a/extern/esm4/lvli.cpp b/extern/esm4/lvli.cpp new file mode 100644 index 000000000..6585d3466 --- /dev/null +++ b/extern/esm4/lvli.cpp @@ -0,0 +1,103 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "lvli.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LeveledItem::LeveledItem() : mFormId(0), mFlags(0), mChanceNone(0), mLvlItemFlags(0), mData(0) +{ + mEditorId.clear(); +} + +ESM4::LeveledItem::~LeveledItem() +{ +} + +void ESM4::LeveledItem::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_LVLD: reader.get(mChanceNone); break; + case ESM4::SUB_LVLF: reader.get(mLvlItemFlags); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_LVLO: + { + static LVLO lvlo; + if (subHdr.dataSize != 12) + { + if (subHdr.dataSize == 8) + { + reader.get(lvlo.level); + reader.get(lvlo.item); + reader.get(lvlo.count); + std::cout << "LVLI " << mEditorId << " LVLO lev " << lvlo.level << ", item " << lvlo.item + << ", count " << lvlo.count << std::endl; + break; + } + else + throw std::runtime_error("ESM4::LVLI::load - " + mEditorId + " LVLO size error"); + } + else + reader.get(lvlo); + + reader.adjustFormId(lvlo.item); + mLvlObject.push_back(lvlo); + break; + } + case ESM4::SUB_OBND: // FO3 + case ESM4::SUB_COED: // FO3 + case ESM4::SUB_LVLG: // FO3 + { + + //std::cout << "LVLI " << ESM4::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LVLI::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::LeveledItem::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LeveledItem::blank() +//{ +//} diff --git a/extern/esm4/lvli.hpp b/extern/esm4/lvli.hpp new file mode 100644 index 000000000..ca87f160d --- /dev/null +++ b/extern/esm4/lvli.hpp @@ -0,0 +1,61 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LVLI_H +#define ESM4_LVLI_H + +#include + +#include "common.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct LeveledItem + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::int8_t mChanceNone; + std::uint8_t mLvlItemFlags; + std::uint8_t mData; + std::vector mLvlObject; + + LeveledItem(); + virtual ~LeveledItem(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LVLI_H diff --git a/extern/esm4/mato.cpp b/extern/esm4/mato.cpp new file mode 100644 index 000000000..9ad5cf7d8 --- /dev/null +++ b/extern/esm4/mato.cpp @@ -0,0 +1,76 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "mato.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Material::Material() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::Material::~Material() +{ +} + +void ESM4::Material::load(ESM4::Reader& reader) +{ + //mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted? + mFormId = reader.hdr().record.id; + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_DNAM: + case ESM4::SUB_DATA: + { + //std::cout << "MATO " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MATO::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Material::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Material::blank() +//{ +//} diff --git a/extern/esm4/mato.hpp b/extern/esm4/mato.hpp new file mode 100644 index 000000000..ac2c4c6aa --- /dev/null +++ b/extern/esm4/mato.hpp @@ -0,0 +1,57 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_MATO_H +#define ESM4_MATO_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Material + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + Material(); + virtual ~Material(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MATO_H diff --git a/extern/esm4/misc.cpp b/extern/esm4/misc.cpp new file mode 100644 index 000000000..ebc90d542 --- /dev/null +++ b/extern/esm4/misc.cpp @@ -0,0 +1,102 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "misc.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::MiscItem::MiscItem() : mFormId(0), mFlags(0), mScript(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::MiscItem::~MiscItem() +{ +} + +void ESM4::MiscItem::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("MISC FULL data read error"); + + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: + case ESM4::SUB_YNAM: + case ESM4::SUB_ZNAM: + case ESM4::SUB_MICO: // FO3 + case ESM4::SUB_RNAM: // FONV + { + //std::cout << "MISC " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MISC::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::MiscItem::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::MiscItem::blank() +//{ +//} diff --git a/extern/esm4/misc.hpp b/extern/esm4/misc.hpp new file mode 100644 index 000000000..9936b2c7a --- /dev/null +++ b/extern/esm4/misc.hpp @@ -0,0 +1,72 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_MISC_H +#define ESM4_MISC_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct MiscItem + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + FormId mScript; + float mBoundRadius; + + Data mData; + + MiscItem(); + virtual ~MiscItem(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MISC_H diff --git a/extern/esm4/navi.cpp b/extern/esm4/navi.cpp new file mode 100644 index 000000000..1e02efd19 --- /dev/null +++ b/extern/esm4/navi.cpp @@ -0,0 +1,373 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "navi.hpp" + +#include +#include + +#include // FIXME: debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +ESM4::Navigation::Navigation() +{ + mEditorId.clear(); +} + +ESM4::Navigation::~Navigation() +{ +} + +void ESM4::Navigation::IslandInfo::load(ESM4::Reader& reader) +{ + reader.get(minX); + reader.get(minY); + reader.get(minZ); + reader.get(maxX); + reader.get(maxY); + reader.get(maxZ); + + std::uint32_t count; + reader.get(count); // countTriangle; + if (count) + { + triangles.resize(count); + //std::cout << "NVMI island triangles " << std::dec << count << std::endl; // FIXME + for (std::vector::iterator it = triangles.begin(); it != triangles.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // countVertex; + if (count) + { + verticies.resize(count); + for (std::vector::iterator it = verticies.begin(); it != verticies.end(); ++it) + { + reader.get(*it); +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "NVMI vert " << std::dec << (*it).x << ", " << (*it).y << ", " << (*it).z << std::endl; +#endif + } + } +} + +void ESM4::Navigation::NavMeshInfo::load(ESM4::Reader& reader) +{ + std::uint32_t count; + + reader.get(formId); + reader.get(flags); + reader.get(x); + reader.get(y); + reader.get(z); + +// FIXME: for debugging only +#if 0 + std::string padding = ""; + if (flags == ESM4::FLG_Modified) + padding.insert(0, 2, '-'); + else if (flags == ESM4::FLG_Unmodified) + padding.insert(0, 4, '.'); + + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "NVMI formId: 0x" << std::hex << formId << std::endl; + std::cout << padding << "NVMI flags: " << std::hex << flags << std::endl; + std::cout << padding << "NVMI center: " << std::dec << x << ", " << y << ", " << z << std::endl; +#endif + + reader.get(flagPrefMerges); + + reader.get(count); // countMerged; + if (count) + { + //std::cout << "NVMI countMerged " << std::dec << count << std::endl; + formIdMerged.resize(count); + for (std::vector::iterator it = formIdMerged.begin(); it != formIdMerged.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // countPrefMerged; + if (count) + { + //std::cout << "NVMI countPrefMerged " << std::dec << count << std::endl; + formIdPrefMerged.resize(count); + for (std::vector::iterator it = formIdPrefMerged.begin(); it != formIdPrefMerged.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // countLinkedDoors; + if (count) + { + //std::cout << "NVMI countLinkedDoors " << std::dec << count << std::endl; + linkedDoors.resize(count); + for (std::vector::iterator it = linkedDoors.begin(); it != linkedDoors.end(); ++it) + { + reader.get(*it); + } + } + + unsigned char island; + reader.get(island); + if (island) + { + Navigation::IslandInfo island; + island.load(reader); + islandInfo.push_back(island); // Maybe don't use a vector for just one entry? + } + else if (flags == FLG_Island) // FIXME: debug only + std::cerr << "nvmi no island but has 0x20 flag" << std::endl; + + reader.get(locationMarker); + + reader.get(worldSpaceId); + //FLG_Tamriel = 0x0000003c, // grid info follows, possibly Tamriel? + //FLG_Morrowind = 0x01380000, // grid info follows, probably Skywind + if (worldSpaceId == 0x0000003c || worldSpaceId == 0x01380000) + { + reader.get(cellGrid.grid.y); // NOTE: reverse order + reader.get(cellGrid.grid.x); +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + if (worldSpaceId == ESM4::FLG_Morrowind) + std::cout << padding << "NVMI MW: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; + else + std::cout << padding << "NVMI SR: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; +#endif + } + else + { + reader.get(cellGrid.cellId); + +#if 0 + if (worldSpaceId == 0) // interior + std::cout << "NVMI Interior: cellId " << std::hex << cellGrid.cellId << std::endl; + else + std::cout << "NVMI FormID: cellId " << std::hex << cellGrid.cellId << std::endl; +#endif + } +} + +// NVPP data seems to be organised this way (total is 0x64 = 100) +// +// (0) total | 0x1 | formid (index 0) | count | formid's +// (1) | count | formid's +// (2) | count | formid's +// (3) | count | formid's +// (4) | count | formid's +// (5) | count | formid's +// (6) | count | formid's +// (7) | count | formid's +// (8) | count | formid's +// (9) | count | formid's +// (10) | 0x1 | formid (index 1) | count | formid's +// (11) | count | formid's +// (12) | count | formid's +// (13) | count | formid's +// (14) | count | formid's +// (15) | count | formid's +// ... +// +// (88) | count | formid's +// (89) | count | formid's +// +// Here the pattern changes (final count is 0xa = 10) +// +// (90) | 0x1 | formid (index 9) | count | formid | index +// (91) | formid | index +// (92) | formid | index +// (93) | formid | index +// (94) | formid | index +// (95) | formid | index +// (96) | formid | index +// (97) | formid | index +// (98) | formid | index +// (99) | formid | index +// +// Note that the index values are not sequential, i.e. the first index value +// (i.e. row 90) for Update.esm is 2. +// +// Also note that there's no list of formid's following the final node (index 9) +// +// The same 10 formids seem to be used for the indicies, but not necessarily +// with the same index value (but only Update.esm differs?) +// +// formid cellid X Y Editor ID other formids in same X,Y S U D D +// -------- ------ --- --- --------------------------- ---------------------------- - - - - +// 00079bbf 9639 5 -4 WhiterunExterior17 00079bc3 0 6 0 0 +// 0010377b 8ed5 6 24 DawnstarWesternMineExterior 1 1 1 1 +// 000a3f44 9577 -22 2 RoriksteadEdge 2 9 2 2 +// 00100f4b 8ea2 26 25 WinterholdExterior01 00100f4a, 00100f49 3 3 3 3 +// 00103120 bc8e 42 -22 (near Riften) 4 2 4 4 +// 00105e9a 929d -18 24 SolitudeExterior03 5 0 5 5 +// 001030cb 7178 -40 1 SalviusFarmExterior01 (east of Markarth) 6 8 6 6 +// 00098776 980b 4 -19 HelgenExterior 000cce3d 7 5 7 7 +// 000e88cc 93de -9 14 (near Morthal) 0010519e, 0010519d, 000e88d2 8 7 8 8 +// 000b87df b51d 33 5 WindhelmAttackStart05 9 4 9 9 +// +void ESM4::Navigation::load(ESM4::Reader& reader) +{ + //mFormId = reader.hdr().record.id; + //mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM4::VER_132 || esmVer == ESM4::VER_133 || esmVer == ESM4::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: // seems to be unused? + { + if (!reader.getZString(mEditorId)) + throw std::runtime_error ("NAVI EDID data read error"); + break; + } + case ESM4::SUB_NVPP: + { + std::uint32_t total; + std::uint32_t count; + reader.get(total); + if (!total) + { + reader.get(count); // throw away + break; + } + + total -= 10; // HACK + std::uint32_t node; + for (std::uint32_t i = 0; i < total; ++i) + { + std::vector preferredPaths; + reader.get(count); + if (count == 1) + { + reader.get(node); + reader.get(count); + } + if (count) + { + preferredPaths.resize(count); + for (std::vector::iterator it = preferredPaths.begin(); + it != preferredPaths.end(); ++it) + { + reader.get(*it); + } + } + mPreferredPaths.push_back(std::make_pair(node, preferredPaths)); +#if 0 + std::cout << "node " << std::hex << node // FIXME: debugging only + << ", count " << count << ", i " << std::dec << i << std::endl; +#endif + } + reader.get(count); + assert(count == 1 && "expected separator"); + + reader.get(node); // HACK + std::vector preferredPaths; + mPreferredPaths.push_back(std::make_pair(node, preferredPaths)); // empty +#if 0 + std::cout << "node " << std::hex << node // FIXME: debugging only + << ", count " << 0 << std::endl; +#endif + + reader.get(count); // HACK + assert(count == 10 && "expected 0xa"); + std::uint32_t index; + for (std::uint32_t i = 0; i < count; ++i) + { + reader.get(node); + reader.get(index); +#if 0 + std::cout << "node " << std::hex << node // FIXME: debugging only + << ", index " << index << ", i " << std::dec << total+i << std::endl; +#endif + std::pair::iterator, bool> res = + mPathIndexMap.insert(std::make_pair(node, index)); + // FIXME: this throws if more than one file is being loaded + //if (!res.second) + //throw std::runtime_error ("node already exists in the preferred path index map"); + } + break; + } + case ESM4::SUB_NVER: + { + std::uint32_t version; // always the same? (0x0c) + reader.get(version); // TODO: store this or use it for merging? + //std::cout << "NAVI version " << std::dec << version << std::endl; + break; + } + case ESM4::SUB_NVMI: // multiple + { + if (esmVer == ESM4::VER_094 || esmVer == ESM4::VER_170 || isFONV) + { + reader.skipSubRecordData(); // FIXME: FO3/FONV have different form of NavMeshInfo + break; + } + + //std::cout << "\nNVMI start" << std::endl; + NavMeshInfo nvmi; + nvmi.load(reader); + mNavMeshInfo.push_back (nvmi); + break; + } + case ESM4::SUB_NVSI: // from Dawnguard onwards + case ESM4::SUB_NVCI: // FO3 + { + reader.skipSubRecordData(); // FIXME: + break; + } + default: + { + throw std::runtime_error("ESM4::NAVI::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } + } +} + +//void ESM4::Navigation::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Navigation::blank() +//{ +//} diff --git a/extern/esm4/navi.hpp b/extern/esm4/navi.hpp new file mode 100644 index 000000000..a53f1b5fe --- /dev/null +++ b/extern/esm4/navi.hpp @@ -0,0 +1,116 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_NAVI_H +#define ESM4_NAVI_H + +#include +#include + +#include "common.hpp" // CellGrid, Vertex + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Navigation + { +#pragma pack(push,1) + struct DoorRef + { + std::uint32_t unknown; + FormId formId; + }; + + struct Triangle + { + std::uint16_t vertexIndex0; + std::uint16_t vertexIndex1; + std::uint16_t vertexIndex2; + }; +#pragma pack(pop) + + struct IslandInfo + { + float minX; + float minY; + float minZ; + float maxX; + float maxY; + float maxZ; + std::vector triangles; + std::vector verticies; + + void load(ESM4::Reader& reader); + }; + + enum Flags // NVMI island flags (not certain) + { + FLG_Island = 0x00000020, + FLG_Modified = 0x00000000, // not island + FLG_Unmodified = 0x00000040 // not island + }; + + struct NavMeshInfo + { + FormId formId; + std::uint32_t flags; + // center point of the navmesh + float x; + float y; + float z; + std::uint32_t flagPrefMerges; + std::vector formIdMerged; + std::vector formIdPrefMerged; + std::vector linkedDoors; + std::vector islandInfo; + std::uint32_t locationMarker; + FormId worldSpaceId; + CellGrid cellGrid; + + void load(ESM4::Reader& reader); + }; + + std::string mEditorId; + + std::vector mNavMeshInfo; + + std::vector > > mPreferredPaths; + + std::map mPathIndexMap; + + Navigation(); + virtual ~Navigation(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_NAVI_H diff --git a/extern/esm4/navm.cpp b/extern/esm4/navm.cpp new file mode 100644 index 000000000..cbd7463fb --- /dev/null +++ b/extern/esm4/navm.cpp @@ -0,0 +1,268 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "navm.hpp" + +#include + +#include // FIXME: debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::NavMesh::NavMesh() : mFormId(0), mFlags(0) +{ +} + +ESM4::NavMesh::~NavMesh() +{ +} + +void ESM4::NavMesh::NVNMstruct::load(ESM4::Reader& reader) +{ + //std::cout << "start: divisor " << std::dec << divisor << ", segments " << triSegments.size() << //std::endl; + //"this 0x" << this << std::endl; // FIXME + + std::uint32_t count; + + reader.get(unknownNVER); + reader.get(unknownLCTN); + reader.get(worldSpaceId); + //FLG_Tamriel = 0x0000003c, // grid info follows, possibly Tamriel? + //FLG_Morrowind = 0x01380000, // grid info follows, probably Skywind + if (worldSpaceId == 0x0000003c || worldSpaceId == 0x01380000) + { + // ^ + // Y | X Y Index + // | 0,0 0 + // 1 |23 0,1 1 + // 0 |01 1,0 2 + // +--- 1,1 3 + // 01 -> + // X + // + // e.g. Dagonfel X:13,14,15,16 Y:43,44,45,46 (Morrowind X:7 Y:22) + // + // Skywind: -4,-3 -2,-1 0,1 2,3 4,5 6,7 + // Morrowind: -2 -1 0 1 2 3 + // + // Formula seems to be floor(Skywind coord / 2) + // + reader.get(cellGrid.grid.y); // NOTE: reverse order + reader.get(cellGrid.grid.x); +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + if (worldSpaceId == ESM4::FLG_Morrowind) + std::cout << padding << "NVNM MW: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; + else + std::cout << padding << "NVNM SR: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; +#endif + } + else + { + reader.get(cellGrid.cellId); + +#if 0 + std::string padding = ""; // FIXME + padding.insert(0, reader.stackSize()*2, ' '); + if (worldSpaceId == 0) // interior + std::cout << padding << "NVNM Interior: cellId " << std::hex << cellGrid.cellId << std::endl; + else + std::cout << padding << "NVNM FormID: cellId " << std::hex << cellGrid.cellId << std::endl; +#endif + } + + reader.get(count); // numVerticies + if (count) + { + verticies.resize(count); + for (std::vector::iterator it = verticies.begin(); it != verticies.end(); ++it) + { + reader.get(*it); +// FIXME: debugging only +#if 0 + //if (reader.hdr().record.id == 0x2004ecc) // FIXME + std::cout << "nvnm vert " << (*it).x << ", " << (*it).y << ", " << (*it).z << std::endl; +#endif + } + } + + reader.get(count); // numTriangles; + if (count) + { + triangles.resize(count); + for (std::vector::iterator it = triangles.begin(); it != triangles.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // numExtConn; + if (count) + { + extConns.resize(count); + for (std::vector::iterator it = extConns.begin(); it != extConns.end(); ++it) + { + reader.get(*it); +// FIXME: debugging only +#if 0 + std::cout << "nvnm ext 0x" << std::hex << (*it).navMesh << std::endl; +#endif + } + } + + reader.get(count); // numDoorTriangles; + if (count) + { + doorTriangles.resize(count); + for (std::vector::iterator it = doorTriangles.begin(); it != doorTriangles.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // numCoverTriangles; + if (count) + { + coverTriangles.resize(count); + for (std::vector::iterator it = coverTriangles.begin(); it != coverTriangles.end(); ++it) + { + reader.get(*it); + } + } + + // abs((maxX - minX) / divisor) = Max X Distance + reader.get(divisor); // FIXME: latest over-writes old + + reader.get(maxXDist); // FIXME: update with formula + reader.get(maxYDist); + reader.get(minX); // FIXME: use std::min + reader.get(minY); + reader.get(minZ); + reader.get(maxX); + reader.get(maxY); + reader.get(maxZ); + + // FIXME: should check remaining size here + // there are divisor^2 segments, each segment is a vector of triangle indicies + for (unsigned int i = 0; i < divisor*divisor; ++i) + { + reader.get(count); // NOTE: count may be zero + + std::vector indicies; + indicies.resize(count); + for (std::vector::iterator it = indicies.begin(); it != indicies.end(); ++it) + { + reader.get(*it); + } + triSegments.push_back(indicies); + } + assert(triSegments.size() == divisor*divisor && "tiangle segments size is not the square of divisor"); +#if 0 + if (triSegments.size() != divisor*divisor) + std::cout << "divisor " << std::dec << divisor << ", segments " << triSegments.size() << //std::endl; + "this 0x" << this << std::endl; +#endif +} + +void ESM4::NavMesh::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + mFlags = reader.hdr().record.flags; + + //std::cout << "NavMesh 0x" << std::hex << this << std::endl; // FIXME + std::uint32_t subSize = 0; // for XXXX sub record + +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "NAVM flags 0x" << std::hex << reader.hdr().record.flags << std::endl; + std::cout << padding << "NAVM id 0x" << std::hex << reader.hdr().record.id << std::endl; +#endif + while (reader.getSubRecordHeader()) + { + switch (reader.subRecordHeader().typeId) + { + case ESM4::SUB_NVNM: + { + NVNMstruct nvnm; + nvnm.load(reader); + mData.push_back(nvnm); // FIXME try swap + break; + } + case ESM4::SUB_ONAM: + case ESM4::SUB_PNAM: + case ESM4::SUB_NNAM: + { + if (subSize) + { + reader.skipSubRecordData(subSize); // special post XXXX + reader.updateRecordRemaining(subSize); // WARNING: manual update + subSize = 0; + } + else + //const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + //std::cout << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + + break; + } + case ESM4::SUB_XXXX: + { + reader.get(subSize); + break; + } + case ESM4::SUB_NVER: // FO3 + case ESM4::SUB_DATA: // FO3 + case ESM4::SUB_NVVX: // FO3 + case ESM4::SUB_NVTR: // FO3 + case ESM4::SUB_NVCA: // FO3 + case ESM4::SUB_NVDP: // FO3 + case ESM4::SUB_NVGD: // FO3 + case ESM4::SUB_NVEX: // FO3 + case ESM4::SUB_EDID: // FO3 + { + reader.skipSubRecordData(); // FIXME: + break; + } + default: + throw std::runtime_error("ESM4::NAVM::load - Unknown subrecord " + + ESM4::printName(reader.subRecordHeader().typeId)); + } + } + //std::cout << "num nvnm " << std::dec << mData.size() << std::endl; // FIXME +} + +//void ESM4::NavMesh::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::NavMesh::blank() +{ +} diff --git a/extern/esm4/navm.hpp b/extern/esm4/navm.hpp new file mode 100644 index 000000000..3bff408f8 --- /dev/null +++ b/extern/esm4/navm.hpp @@ -0,0 +1,111 @@ +/* + Copyright (C) 2015, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_NAVM_H +#define ESM4_NAVM_H + +#include + +#include "common.hpp" // CellGrid, Vertex + +namespace ESM4 +{ + class Reader; + class Writer; + + struct NavMesh + { +#pragma pack(push,1) + struct Triangle + { + std::uint16_t vertexIndex0; + std::uint16_t vertexIndex1; + std::uint16_t vertexIndex2; + std::uint16_t edge0; + std::uint16_t edge1; + std::uint16_t edge2; + std::uint16_t coverMarker; + std::uint16_t coverFlags; + }; + + struct ExtConnection + { + std::uint32_t unknown; + FormId navMesh; + std::uint16_t triangleIndex; + }; + + struct DoorTriangle + { + std::uint16_t triangleIndex; + std::uint32_t unknown; + FormId doorRef; + }; +#pragma pack(pop) + + struct NVNMstruct + { + std::uint32_t unknownNVER; + std::uint32_t unknownLCTN; + FormId worldSpaceId; + CellGrid cellGrid; + std::vector verticies; + std::vector triangles; + std::vector extConns; + std::vector doorTriangles; + std::vector coverTriangles; + std::uint32_t divisor; + float maxXDist; + float maxYDist; + float minX; + float minY; + float minZ; + float maxX; + float maxY; + float maxZ; + // there are divisor^2 segments, each segment is a vector of triangle indicies + std::vector > triSegments; + + void load(ESM4::Reader& esm); + }; + + std::vector mData; // Up to 4 skywind cells in one Morrowind cell + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + NavMesh(); + virtual ~NavMesh(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; + +} + +#endif // ESM4_NAVM_H diff --git a/extern/esm4/npc_.cpp b/extern/esm4/npc_.cpp new file mode 100644 index 000000000..1d8227f9b --- /dev/null +++ b/extern/esm4/npc_.cpp @@ -0,0 +1,237 @@ +/* + Copyright (C) 2016-2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "npc_.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Npc::Npc() : mFormId(0), mFlags(0), mRace(0), mClass(0), mHair(0), mEyes(0), mDeathItem(0), + mScript(0), mCombatStyle(0), mSoundBase(0), mSound(0), mSoundChance(0), + mFootWeight(0.f), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + + std::memset(&mAIData, 0, sizeof(AIData)); + std::memset(&mData, 0, sizeof(Data)); + std::memset(&mBaseConfig, 0, sizeof(ActorBaseConfig)); + std::memset(&mFaction, 0, sizeof(ActorFaction)); +} + +ESM4::Npc::~Npc() +{ +} + +void ESM4::Npc::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM4::VER_132 || esmVer == ESM4::VER_133 || esmVer == ESM4::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("NPC_ FULL data read error"); + + break; + } + case ESM4::SUB_CNTO: + { + static InventoryItem inv; // FIXME: use unique_ptr here? + reader.get(inv); + reader.adjustFormId(inv.item); + mInventory.push_back(inv); + break; + } + case ESM4::SUB_SPLO: + { + FormId id; + reader.getFormId(id); + mSpell.push_back(id); + break; + } + case ESM4::SUB_PKID: + { + FormId id; + reader.getFormId(id); + mAIPackages.push_back(id); + break; + } + case ESM4::SUB_SNAM: + { + reader.get(mFaction); + reader.adjustFormId(mFaction.faction); + break; + } + case ESM4::SUB_RNAM: reader.getFormId(mRace); break; + case ESM4::SUB_CNAM: reader.getFormId(mClass); break; + case ESM4::SUB_HNAM: reader.getFormId(mHair); break; + case ESM4::SUB_ENAM: reader.getFormId(mEyes); break; + // + case ESM4::SUB_INAM: reader.getFormId(mDeathItem); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + // + case ESM4::SUB_AIDT: + { + if (esmVer == ESM4::VER_094 || esmVer == ESM4::VER_170 || isFONV) + { + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + break; + } + + reader.get(mAIData); + break; + } + case ESM4::SUB_ACBS: + { + if (esmVer == ESM4::VER_094 || esmVer == ESM4::VER_170 || isFONV) + { + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + break; + } + + reader.get(mBaseConfig); + break; + } + case ESM4::SUB_DATA: + { + if (esmVer == ESM4::VER_094 || esmVer == ESM4::VER_170 || isFONV) + { + if (subHdr.dataSize != 0) // FIXME FO3 + reader.skipSubRecordData(); + break; // zero length + } + + reader.get(&mData, 33); // FIXME: check packing + break; + } + case ESM4::SUB_ZNAM: reader.getFormId(mCombatStyle); break; + case ESM4::SUB_CSCR: reader.getFormId(mSoundBase); break; + case ESM4::SUB_CSDI: reader.getFormId(mSound); break; + case ESM4::SUB_CSDC: reader.get(mSoundChance); break; + case ESM4::SUB_WNAM: reader.get(mFootWeight); break; + // + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_KFFZ: + { + std::string str; + if (!reader.getZString(str)) + throw std::runtime_error ("NPC_ KFFZ data read error"); + + // Seems to be only below 3, and only happens 3 times while loading TES4: + // Forward_SheogorathWithCane.kf + // TurnLeft_SheogorathWithCane.kf + // TurnRight_SheogorathWithCane.kf + std::stringstream ss(str); + std::string file; + while (std::getline(ss, file, '\0')) // split the strings + mKf.push_back(file); + + break; + } + case ESM4::SUB_LNAM: + case ESM4::SUB_HCLR: + case ESM4::SUB_FGGS: + case ESM4::SUB_FGGA: + case ESM4::SUB_FGTS: + case ESM4::SUB_FNAM: + case ESM4::SUB_ATKR: + case ESM4::SUB_COCT: + case ESM4::SUB_CRIF: + case ESM4::SUB_CSDT: + case ESM4::SUB_DNAM: + case ESM4::SUB_DOFT: + case ESM4::SUB_DPLT: + case ESM4::SUB_ECOR: + case ESM4::SUB_ANAM: + case ESM4::SUB_ATKD: + case ESM4::SUB_ATKE: + case ESM4::SUB_DEST: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: + case ESM4::SUB_FTST: + case ESM4::SUB_HCLF: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_NAM5: + case ESM4::SUB_NAM6: + case ESM4::SUB_NAM7: + case ESM4::SUB_NAM8: + case ESM4::SUB_NAM9: + case ESM4::SUB_NAMA: + case ESM4::SUB_OBND: + case ESM4::SUB_PNAM: + case ESM4::SUB_PRKR: + case ESM4::SUB_PRKZ: + case ESM4::SUB_QNAM: + case ESM4::SUB_SOFT: + case ESM4::SUB_SPCT: + case ESM4::SUB_TIAS: + case ESM4::SUB_TINC: + case ESM4::SUB_TINI: + case ESM4::SUB_TINV: + case ESM4::SUB_TPLT: + case ESM4::SUB_VMAD: + case ESM4::SUB_VTCK: + case ESM4::SUB_GNAM: + case ESM4::SUB_SHRT: + case ESM4::SUB_SPOR: + case ESM4::SUB_EAMT: // FO3 + case ESM4::SUB_NAM4: // FO3 + case ESM4::SUB_COED: // FO3 + { + //std::cout << "NPC_ " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::NPC_::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Npc::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Npc::blank() +//{ +//} diff --git a/extern/esm4/npc_.hpp b/extern/esm4/npc_.hpp new file mode 100644 index 000000000..27bc0e8c5 --- /dev/null +++ b/extern/esm4/npc_.hpp @@ -0,0 +1,115 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_NPC__H +#define ESM4_NPC__H + +#include + +#include "common.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Npc + { + struct SkillValues + { + std::uint8_t armorer; + std::uint8_t athletics; + std::uint8_t blade; + std::uint8_t block; + std::uint8_t blunt; + std::uint8_t handToHand; + std::uint8_t heavyArmor; + std::uint8_t alchemy; + std::uint8_t alteration; + std::uint8_t conjuration; + std::uint8_t destruction; + std::uint8_t illusion; + std::uint8_t mysticism; + std::uint8_t restoration; + std::uint8_t acrobatics; + std::uint8_t lightArmor; + std::uint8_t marksman; + std::uint8_t mercantile; + std::uint8_t security; + std::uint8_t sneak; + std::uint8_t speechcraft; + }; + + struct Data + { + SkillValues skills; + std::uint32_t health; + AttributeValues attribs; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + FormId mRace; + FormId mClass; + FormId mHair; + FormId mEyes; + + FormId mDeathItem; + std::vector mSpell; + FormId mScript; + + AIData mAIData; + std::vector mAIPackages; + ActorBaseConfig mBaseConfig; + ActorFaction mFaction; + Data mData; + FormId mCombatStyle; + FormId mSoundBase; + FormId mSound; + std::uint8_t mSoundChance; + float mFootWeight; + + float mBoundRadius; + std::vector mKf; // filenames only, get directory path from mModel + + std::vector mInventory; + + Npc(); + virtual ~Npc(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_NPC__H diff --git a/extern/esm4/race.cpp b/extern/esm4/race.cpp new file mode 100644 index 000000000..1940dcfc2 --- /dev/null +++ b/extern/esm4/race.cpp @@ -0,0 +1,229 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "race.hpp" + +#include +#include // FIXME: debugging only +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Race::Race() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.flags = 0; +} + +ESM4::Race::~Race() +{ +} + +void ESM4::Race::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: + { + reader.getZString(mEditorId); + std::cout << "RACE " << mEditorId << std::endl; + break; + } + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("RACE FULL data read error"); + + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; // Only in TES4? + //case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + //case ESM4::SUB_MODT: + case ESM4::SUB_DESC: //skipping...1 <- different lenghts + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mDesc); // TODO check if formid is null + else if (!reader.getZString(mDesc)) + throw std::runtime_error ("RACE DESC data read error"); + + break; + } + case ESM4::SUB_ATTR: //skipping...16 // Only in TES4? guess - 8 attrib each for male/female? + // Argonian 28 28 1e 32 32 1e 1e 32 28 32 28 28 28 1e 1e 32 + // 40 40 30 50 50 30 30 50 40 50 40 40 40 30 30 50 + // Nord 32 1e 1e 28 28 32 1e 32 32 1e 28 28 28 28 1e 32 + // 50 30 30 40 40 50 30 50 50 30 40 40 40 40 30 50 + // StrIntWilAglSpdEndPerLuk + // Male Female + case ESM4::SUB_CNAM: //skipping...1 // Only in TES4? + // Sheogorath 0x00 + // Golden Saint 0x03 + // Dark Seducer 0x0C + // Vampire Race 0x00 + // Dremora 0x07 + // Argonian 0x00 + // Nord 0x05 + // Breton 0x05 + // Wood Elf 0x0D + // khajiit 0x05 + // Dark Elf 0x00 + // Orc 0x0C + // High Elf 0x0F + // Redguard 0x0D + // Imperial 0x0D + case ESM4::SUB_DATA: //skipping...36 // ?? different length to TES5 + // Altimer + // + // hex 13 05 14 0a 15 05 16 0a 17 05 18 0a ff 00 00 00 + // dec 5 10 5 10 5 10 -1 0 + // alc alt conj dest illu myst none unknown (always 00 00) + // + // cd cc 8c 3f : 1.1 height Male + // cd cc 8c 3f : 1.1 height Female + // 00 00 80 3f : 1.0 weihgt Male + // 00 00 80 3f : 1.0 weight Female + // 01 00 00 00 fist byte 1 means playable? uint32_t flag? + // + // Redguard + // + // hex 0d 0a 10 0a 12 05 1b 05 0e 0a 1d 05 ff 00 00 00 + // dec 10 10 5 5 10 5 -1 + // ath blun h.arm l.arm blade merch + // + // + // 0a d7 83 3f : 1.03 height Male + // 00 00 80 3f : 1.0 height Female + // 0a d7 83 3f : 1.03 weight Male + // 00 00 80 3f : 1.0 weight Female + // 01 00 00 00 + // + // skill index + // 0x0C Armorer + // 0x0D Athletics + // 0x0E Blade + // 0x0F Block + // 0x10 Blunt + // 0x11 HandToHand + // 0x12 HeavyArmor + // 0x13 Alchemy + // 0x14 Alteration + // 0x15 Conjuration + // 0x16 Destruction + // 0x17 Illusion + // 0x18 Mysticism + // 0x19 Restoration + // 0x1A Acrobatics + // 0x1B LightArmor + // 0x1C Marksman + // 0x1D Mercantile + // 0x1E Security + // 0x1F Sneak + // 0x20 Speechcraft + case ESM4::SUB_SNAM: //skipping...2 // only in TES4? + case ESM4::SUB_XNAM: //skipping...8 // only in TES4? Often has similar numbers to VNAM + case ESM4::SUB_ENAM: //skipping...0 <- different lengthts, maybe formids for EYES? + case ESM4::SUB_HNAM: //skipping...0 <- different lengthts, maybe formids for HAIR? + case ESM4::SUB_VNAM: //skipping...8 // equipment type flags meant to be uint32 ??? + // GLOB reference? shows up in SCRO in sript + // records and CTDA in INFO records + { + std::cout << "RACE " << ESM4::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; + // For debugging only +//#if 0 + unsigned char mDataBuf[256/*bufSize*/]; + reader.get(&mDataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + for (unsigned int i = 0; i < subHdr.dataSize; ++i) + { + //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) + //ss << (char)(mDataBuf[i]) << " "; + //else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) + ss << "\n"; + else if (i < 256/*bufSize*/-1) + ss << " "; + } + std::cout << ss.str() << std::endl; + + //reader.skipSubRecordData(); + break; + } +//#endif + case ESM4::SUB_DNAM: //skipping...8 // decapitate armor, 2 formids + case ESM4::SUB_FGGA: //skipping...120 // prob face gen stuff + case ESM4::SUB_FGGS: //skipping...200 // prob face gen stuff + case ESM4::SUB_FGTS: //skipping...200 // prob face gen stuff + case ESM4::SUB_FNAM: //skipping...0 // start marker female model + case ESM4::SUB_INDX: //skipping...4 // marker preceding egt models? uint32 always 0 + case ESM4::SUB_MNAM: //skipping...0 // start marker male model + case ESM4::SUB_NAM0: //skipping...0 // start marker head data + case ESM4::SUB_NAM1: //skipping...0 // strat marker egt models + case ESM4::SUB_PNAM: //skipping...4 // face gen main clamp float + case ESM4::SUB_SPLO: //skipping...4 // bonus spell formid (TES5 may have SPCT and multiple SPLO) + case ESM4::SUB_UNAM: //skipping...4 // face gen face clamp float + case ESM4::SUB_YNAM: // FO3 + case ESM4::SUB_NAM2: // FO3 + case ESM4::SUB_VTCK: // FO3 + case ESM4::SUB_MODT: // FO3 + case ESM4::SUB_MODD: // FO3 + case ESM4::SUB_ONAM: // FO3 + { + + //std::cout << "RACE " << ESM4::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::RACE::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Race::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Race::blank() +//{ +//} diff --git a/extern/esm4/race.hpp b/extern/esm4/race.hpp new file mode 100644 index 000000000..b7861c22a --- /dev/null +++ b/extern/esm4/race.hpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_RACE +#define ESM4_RACE + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Race + { +#pragma pack(push, 1) + struct Data + { + std::uint8_t flags; // 0x01 = not playable, 0x02 = not male, 0x04 = not female, ?? = fixed + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + std::string mDesc; + + float mBoundRadius; + + Data mData; + + Race(); + virtual ~Race(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_RACE diff --git a/extern/esm4/reader.cpp b/extern/esm4/reader.cpp new file mode 100644 index 000000000..c8d376f49 --- /dev/null +++ b/extern/esm4/reader.cpp @@ -0,0 +1,560 @@ +/* + Copyright (C) 2015-2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#include "reader.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include "formid.hpp" + +#ifdef NDEBUG // FIXME: debugging only +#undef NDEBUG +#endif + +ESM4::Reader::Reader() : mObserver(nullptr), mRecordRemaining(0), mCellGridValid(false) +{ + mCtx.modIndex = 0; + mCtx.currWorld = 0; + mCtx.currCell = 0; + mCtx.recHeaderSize = sizeof(ESM4::RecordHeader); + + mInBuf.reset(); + mDataBuf.reset(); + mStream.setNull(); + mSavedStream.setNull(); +} + +ESM4::Reader::~Reader() +{ +} + +// Since the record data may have been compressed, it is not always possible to use seek() to +// go to a position of a sub record. +// +// The record header needs to be saved in the context or the header needs to be re-loaded after +// restoring the context. The latter option was chosen. +ESM4::ReaderContext ESM4::Reader::getContext() +{ + mCtx.filePos = mStream->tell() - mCtx.recHeaderSize; // update file position + return mCtx; +} + +// NOTE: Assumes that the caller has reopened the file if necessary +bool ESM4::Reader::restoreContext(const ESM4::ReaderContext& ctx) +{ + if (!mSavedStream.isNull()) + { + mStream = mSavedStream; + mSavedStream.setNull(); + } + + mCtx.groupStack.clear(); // probably not necessary? + mCtx = ctx; + mStream->seek(ctx.filePos); // update file position + + //return getRecordHeader(); // can't use it because mStream may have been switched + + if (mObserver) + mObserver->update(mCtx.recHeaderSize); + + return (mStream->read(&mRecordHeader, mCtx.recHeaderSize) == mCtx.recHeaderSize + && (mRecordRemaining = mRecordHeader.record.dataSize)); // for keeping track of sub records +} + +bool ESM4::Reader::skipNextGroupCellChild() +{ + if (mStream->eof()) + return false; + + std::size_t pos = mStream->tell(); // save + ESM4::RecordHeader hdr; + if (!mStream->read(&hdr, mCtx.recHeaderSize)) + throw std::runtime_error("ESM4::Reader::could not peek header"); + + if (hdr.group.type != ESM4::Grp_CellChild) + { + mStream->seek(pos); // go back to saved + return false; + } + + mCtx.groupStack.back().second -= hdr.group.groupSize; + mStream->skip(hdr.group.groupSize - (std::uint32_t)mCtx.recHeaderSize); // already read the header + if (mObserver) + mObserver->update(hdr.group.groupSize); + + return true; +} + +// TODO: consider checking file path using boost::filesystem::exists() +std::size_t ESM4::Reader::openTes4File(const std::string& name) +{ + mCtx.filename = name; + mStream = Ogre::DataStreamPtr(new Ogre::FileStreamDataStream( + OGRE_NEW_T(std::ifstream(name.c_str(), std::ios_base::binary), + Ogre::MEMCATEGORY_GENERAL), /*freeOnClose*/true)); + return mStream->size(); +} + +void ESM4::Reader::setRecHeaderSize(const std::size_t size) +{ + mCtx.recHeaderSize = size; +} + +void ESM4::Reader::registerForUpdates(ESM4::ReaderObserver *observer) +{ + mObserver = observer; +} + +// FIXME: only "English" strings supported for now +void ESM4::Reader::buildLStringIndex() +{ + if ((mHeader.mFlags & Rec_ESM) == 0 || (mHeader.mFlags & Rec_Localized) == 0) + return; + + boost::filesystem::path p(mCtx.filename); + std::string filename = p.stem().filename().string(); + + buildLStringIndex("Strings/" + filename + "_English.STRINGS", Type_Strings); + buildLStringIndex("Strings/" + filename + "_English.ILSTRINGS", Type_ILStrings); + buildLStringIndex("Strings/" + filename + "_English.DLSTRINGS", Type_DLStrings); +} + +void ESM4::Reader::buildLStringIndex(const std::string& stringFile, LocalizedStringType stringType) +{ + std::uint32_t numEntries; + std::uint32_t dataSize; + std::uint32_t stringId; + LStringOffset sp; + sp.type = stringType; + + // TODO: possibly check if the resource exists? + Ogre::DataStreamPtr filestream = Ogre::ResourceGroupManager::getSingleton().openResource(stringFile); + + switch (stringType) + { + case Type_Strings: mStrings = filestream; break; + case Type_ILStrings: mILStrings = filestream; break; + case Type_DLStrings: mDLStrings = filestream; break; + default: + throw std::runtime_error("ESM4::Reader::unexpected string type"); + } + + filestream->read(&numEntries, sizeof(numEntries)); + filestream->read(&dataSize, sizeof(dataSize)); + std::size_t dataStart = filestream->size() - dataSize; + for (unsigned int i = 0; i < numEntries; ++i) + { + filestream->read(&stringId, sizeof(stringId)); + filestream->read(&sp.offset, sizeof(sp.offset)); + sp.offset += (std::uint32_t)dataStart; + mLStringIndex[stringId] = sp; + } + //assert(dataStart - filestream->tell() == 0 && "String file start of data section mismatch"); +} + +void ESM4::Reader::getLocalizedString(std::string& str) +{ + std::uint32_t stringId; + get(stringId); + getLocalizedString(stringId, str); +} + +// FIXME: very messy and probably slow/inefficient +void ESM4::Reader::getLocalizedString(const FormId stringId, std::string& str) +{ + const std::map::const_iterator it = mLStringIndex.find(stringId); + + if (it != mLStringIndex.end()) + { + Ogre::DataStreamPtr filestream; + + switch (it->second.type) + { + case Type_Strings: + { + filestream = mStrings; + filestream->seek(it->second.offset); + + char ch; + std::vector data; + do { + filestream->read(&ch, sizeof(ch)); + data.push_back(ch); + } while (ch != 0); + + str = std::string(data.data()); + return; + } + case Type_ILStrings: filestream = mILStrings; break; + case Type_DLStrings: filestream = mDLStrings; break; + default: + throw std::runtime_error("ESM4::Reader::getLocalizedString unexpected string type"); + } + + // get ILStrings or DLStrings + filestream->seek(it->second.offset); + getZString(str, filestream); + } + else + throw std::runtime_error("ESM4::Reader::getLocalizedString localized string not found"); +} + +bool ESM4::Reader::getRecordHeader() +{ + // FIXME: this seems very hacky but we may have skipped subrecords from within an inflated data block + if (/*mStream->eof() && */!mSavedStream.isNull()) + { + mStream = mSavedStream; + mSavedStream.setNull(); + } + + // keep track of data left to read from the file + // FIXME: having a default instance of mObserver might be faster than checking for null all the time? + if (mObserver) + mObserver->update(mCtx.recHeaderSize); + + return (mStream->read(&mRecordHeader, mCtx.recHeaderSize) == mCtx.recHeaderSize + && (mRecordRemaining = mRecordHeader.record.dataSize)); // for keeping track of sub records + + // After reading the record header we can cache a WRLD or CELL formId for convenient access later. + // (currently currWorld and currCell are set manually when loading the WRLD and CELL records) +} + +bool ESM4::Reader::getSubRecordHeader() +{ + bool result = false; + // NOTE: some SubRecords have 0 dataSize (e.g. SUB_RDSD in one of REC_REGN records in Oblivion.esm). + // Also SUB_XXXX has zero dataSize and the following 4 bytes represent the actual dataSize + // - hence it require manual updtes to mRecordRemaining. See ESM4::NavMesh and ESM4::World. + if (mRecordRemaining >= sizeof(mSubRecordHeader)) + { + result = get(mSubRecordHeader); + mRecordRemaining -= (sizeof(mSubRecordHeader) + mSubRecordHeader.dataSize); + } + return result; +} + +// NOTE: the parameter 'files' must have the file names in the loaded order +void ESM4::Reader::updateModIndicies(const std::vector& files) +{ + if (files.size() >= 0xff) + throw std::runtime_error("ESM4::Reader::updateModIndicies too many files"); // 0xff is reserved + + // NOTE: this map is rebuilt each time this method is called (i.e. each time a file is loaded) + // Perhaps there is an opportunity to optimize this by saving the result somewhere. + // But then, the number of files is at most around 250 so perhaps keeping it simple might be better. + + // build a lookup map + std::unordered_map fileIndex; + + for (size_t i = 0; i < files.size(); ++i) // ATTENTION: assumes current file is not included + fileIndex[boost::to_lower_copy(files[i])] = i; + + mHeader.mModIndicies.resize(mHeader.mMaster.size()); + for (unsigned int i = 0; i < mHeader.mMaster.size(); ++i) + { + // locate the position of the dependency in already loaded files + std::unordered_map::const_iterator it + = fileIndex.find(boost::to_lower_copy(mHeader.mMaster[i].name)); + + if (it != fileIndex.end()) + mHeader.mModIndicies[i] = (std::uint32_t)((it->second << 24) & 0xff000000); + else + throw std::runtime_error("ESM4::Reader::updateModIndicies required dependency file not loaded"); +#if 0 + std::cout << "Master Mod: " << mHeader.mMaster[i].name << ", " // FIXME: debugging only + << ESM4::formIdToString(mHeader.mModIndicies[i]) << std::endl; +#endif + } + + if (!mHeader.mModIndicies.empty() && mHeader.mModIndicies[0] != 0) + throw std::runtime_error("ESM4::Reader::updateModIndicies base modIndex is not zero"); +} + +void ESM4::Reader::saveGroupStatus() +{ +#if 0 + std::string padding = ""; // FIXME: debugging only + padding.insert(0, mCtx.groupStack.size()*2, ' '); + std::cout << padding << "Starting record group " + << ESM4::printLabel(mRecordHeader.group.label, mRecordHeader.group.type) << std::endl; +#endif + if (mRecordHeader.group.groupSize == (std::uint32_t)mCtx.recHeaderSize) + { +#if 0 + std::cout << padding << "Igorning record group " // FIXME: debugging only + << ESM4::printLabel(mRecordHeader.group.label, mRecordHeader.group.type) + << " (empty)" << std::endl; +#endif + if (!mCtx.groupStack.empty()) // top group may be empty (e.g. HAIR in Skyrim) + { + // don't put on the stack, checkGroupStatus() may not get called before recursing into this method + mCtx.groupStack.back().second -= mRecordHeader.group.groupSize; + checkGroupStatus(); + } + return; // DLCMehrunesRazor - Unofficial Patch.esp is at EOF after one of these empty groups... + } + + // push group + mCtx.groupStack.push_back(std::make_pair(mRecordHeader.group, + mRecordHeader.group.groupSize - (std::uint32_t)mCtx.recHeaderSize)); +} + +const ESM4::CellGrid& ESM4::Reader::currCellGrid() const +{ + // Maybe should throw an exception instead? + assert(mCellGridValid && "Attempt to use an invalid cell grid"); + + return mCurrCellGrid; +} + +void ESM4::Reader::checkGroupStatus() +{ + // pop finished groups + while (!mCtx.groupStack.empty() && mCtx.groupStack.back().second == 0) + { + ESM4::GroupTypeHeader grp = mCtx.groupStack.back().first; // FIXME: grp is for debugging only + + uint32_t groupSize = mCtx.groupStack.back().first.groupSize; + mCtx.groupStack.pop_back(); +#if 0 + std::string padding = ""; // FIXME: debugging only + padding.insert(0, mCtx.groupStack.size()*2, ' '); + std::cout << padding << "Finished record group " << ESM4::printLabel(grp.label, grp.type) << std::endl; +#endif + // Check if the previous group was the final one + if (mCtx.groupStack.empty()) + return; + + //assert (mCtx.groupStack.back().second >= groupSize && "Read more records than available"); +#if 0 + if (mCtx.groupStack.back().second < groupSize) // FIXME: debugging only + std::cerr << ESM4::printLabel(mCtx.groupStack.back().first.label, + mCtx.groupStack.back().first.type) + << " read more records than available" << std::endl; +#endif + mCtx.groupStack.back().second -= groupSize; + } +} + +// WARNING: this method should be used after first calling saveGroupStatus() +const ESM4::GroupTypeHeader& ESM4::Reader::grp(std::size_t pos) const +{ + assert(pos <= mCtx.groupStack.size()-1 && "ESM4::Reader::grp - exceeded stack depth"); + + return (*(mCtx.groupStack.end()-pos-1)).first; +} + +void ESM4::Reader::getRecordData() +{ + std::uint32_t bufSize = 0; + + if ((mRecordHeader.record.flags & ESM4::Rec_Compressed) != 0) + { + mInBuf.reset(new unsigned char[mRecordHeader.record.dataSize-(int)sizeof(bufSize)]); + mStream->read(&bufSize, sizeof(bufSize)); + mStream->read(mInBuf.get(), mRecordHeader.record.dataSize-(int)sizeof(bufSize)); + mDataBuf.reset(new unsigned char[bufSize]); + + int ret; + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = bufSize; + strm.next_in = mInBuf.get(); + ret = inflateInit(&strm); + if (ret != Z_OK) + throw std::runtime_error("ESM4::Reader::getRecordData - inflateInit failed"); + + strm.avail_out = bufSize; + strm.next_out = mDataBuf.get(); + ret = inflate(&strm, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR && "ESM4::Reader::getRecordData - inflate - state clobbered"); + switch (ret) + { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: //FONV.esm 0xB0CFF04 LAND record zlip DATA_ERROR + case Z_MEM_ERROR: + inflateEnd(&strm); + getRecordDataPostActions(); + throw std::runtime_error("ESM4::Reader::getRecordData - inflate failed"); + } + assert(ret == Z_OK || ret == Z_STREAM_END); + + // For debugging only +#if 0 + std::ostringstream ss; + for (unsigned int i = 0; i < bufSize; ++i) + { + if (mDataBuf[i] > 64 && mDataBuf[i] < 91) + ss << (char)(mDataBuf[i]) << " "; + else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) + ss << "\n"; + else if (i < bufSize-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +#endif + inflateEnd(&strm); + + mSavedStream = mStream; + mStream = Ogre::DataStreamPtr(new Ogre::MemoryDataStream(mDataBuf.get(), bufSize, false, true)); + } + + getRecordDataPostActions(); + //std::cout << "data size 0x" << std::hex << mRecordHeader.record.dataSize << std::endl; // FIXME: debug only +} + +void ESM4::Reader::getRecordDataPostActions() +{ + // keep track of data left to read from the current group + assert (!mCtx.groupStack.empty() && "Read data for a record without a group"); + mCtx.groupStack.back().second -= (std::uint32_t)mCtx.recHeaderSize + mRecordHeader.record.dataSize; + + // keep track of data left to read from the file + if (mObserver) + mObserver->update(mRecordHeader.record.dataSize); +} + +bool ESM4::Reader::getZString(std::string& str) +{ + return getZString(str, mStream); +} + +// FIXME: how to without using a temp buffer? +bool ESM4::Reader::getZString(std::string& str, Ogre::DataStreamPtr filestream) +{ + std::uint32_t size = 0; + if (filestream == mStream) + size = mSubRecordHeader.dataSize; // WARNING: assumed size from the header is correct + else + filestream->read(&size, sizeof(size)); + + boost::scoped_array buf(new char[size]); + if (filestream->read(buf.get(), size) == (size_t)size) + { + + if (buf[size - 1] != 0) + { + str.assign(buf.get(), size); + //std::cerr << "ESM4::Reader::getZString string is not terminated with a zero" << std::endl; + } + else + str.assign(buf.get(), size - 1);// don't copy null terminator + + //assert((size_t)size-1 == str.size() && "ESM4::Reader::getZString string size mismatch"); + return true; + } + else + { + str.clear(); + return false; // FIXME: throw instead? + } +} + +// Assumes that saveGroupStatus() is not called before this (hence we don't update mCtx.groupStack) +void ESM4::Reader::skipGroup() +{ +#if 0 + std::string padding = ""; // FIXME: debugging only + padding.insert(0, mCtx.groupStack.size()*2, ' '); + std::cout << padding << "Skipping record group " + << ESM4::printLabel(mRecordHeader.group.label, mRecordHeader.group.type) << std::endl; +#endif + // Note: subtract the size of header already read before skipping + mStream->skip(mRecordHeader.group.groupSize - (std::uint32_t)mCtx.recHeaderSize); + + // keep track of data left to read from the file + if (mObserver) + mObserver->update((std::size_t)mRecordHeader.group.groupSize - mCtx.recHeaderSize); + + if (!mCtx.groupStack.empty()) + mCtx.groupStack.back().second -= mRecordHeader.group.groupSize; +} + +void ESM4::Reader::skipRecordData() +{ + mStream->skip(mRecordHeader.record.dataSize); + + // keep track of data left to read from the current group + assert (!mCtx.groupStack.empty() && "Skipping a record without a group"); + mCtx.groupStack.back().second -= (std::uint32_t)mCtx.recHeaderSize + mRecordHeader.record.dataSize; + + // keep track of data left to read from the file + if (mObserver) + mObserver->update(mRecordHeader.record.dataSize); +} + +void ESM4::Reader::skipSubRecordData() +{ + mStream->skip(mSubRecordHeader.dataSize); +} + +void ESM4::Reader::skipSubRecordData(std::uint32_t size) +{ + mStream->skip(size); +} + +// ModIndex adjusted formId according to master file dependencies +// (see http://www.uesp.net/wiki/Tes4Mod:FormID_Fixup) +// NOTE: need to update modindex to mModIndicies.size() before saving +void ESM4::Reader::adjustFormId(FormId& id) +{ + if (mHeader.mModIndicies.empty()) + return; + + unsigned int index = (id >> 24) & 0xff; + + if (index < mHeader.mModIndicies.size()) + id = mHeader.mModIndicies[index] | (id & 0x00ffffff); + else + id = mCtx.modIndex | (id & 0x00ffffff); +} + +bool ESM4::Reader::getFormId(FormId& id) +{ + if (!get(id)) + return false; + + adjustFormId(id); + return true; +} + +void ESM4::Reader::adjustGRUPFormId() +{ + adjustFormId(mRecordHeader.group.label.value); +} diff --git a/extern/esm4/reader.hpp b/extern/esm4/reader.hpp new file mode 100644 index 000000000..ce52b70c7 --- /dev/null +++ b/extern/esm4/reader.hpp @@ -0,0 +1,278 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#ifndef ESM4_READER_H +#define ESM4_READER_H + +#include +#include +#include + +#include + +#include + +#include "common.hpp" +#include "tes4.hpp" + +namespace ESM4 +{ + class ReaderObserver + { + public: + ReaderObserver() {} + virtual ~ReaderObserver() {} + + virtual void update(std::size_t size) = 0; + }; + + typedef std::vector > GroupStack; + + struct ReaderContext + { + std::string filename; // from openTes4File() + std::uint32_t modIndex; // the sequential position of this file in the load order: + // 0x00 reserved, 0xFF in-game (see notes below) + + GroupStack groupStack; // keep track of bytes left to find when a group is done + + FormId currWorld; // formId of current world - for grouping CELL records + FormId currCell; // formId of current cell + + std::size_t recHeaderSize; // normally should be already set correctly, but just in + // case the file was re-opened. default = TES5 size, + // can be reduced for TES4 by setRecHeaderSize() + + std::size_t filePos; // assume that the record header will be re-read once + // the context is restored. + }; + + class Reader + { + ReaderObserver *mObserver; // observer for tracking bytes read + + Header mHeader; // ESM header // FIXME + RecordHeader mRecordHeader; // header of the current record or group being processed + SubRecordHeader mSubRecordHeader; // header of the current sub record being processed + std::size_t mRecordRemaining; // number of bytes to be read by sub records following current + + // FIXME: try to get rid of these two members, seem like massive hacks + CellGrid mCurrCellGrid; // TODO: should keep a map of cell formids + bool mCellGridValid; + + ReaderContext mCtx; + + // Use scoped arrays to avoid memory leak due to exceptions, etc. + // TODO: try fixed size buffers on the stack for both buffers (may be faster) + boost::scoped_array mInBuf; + boost::scoped_array mDataBuf; + + Ogre::DataStreamPtr mStream; + Ogre::DataStreamPtr mSavedStream; // mStream is saved here while using deflated memory stream + + Ogre::DataStreamPtr mStrings; + Ogre::DataStreamPtr mILStrings; + Ogre::DataStreamPtr mDLStrings; + + enum LocalizedStringType + { + Type_Strings = 0, + Type_ILStrings = 1, + Type_DLStrings = 2 + }; + + struct LStringOffset + { + LocalizedStringType type; + std::uint32_t offset; + }; + std::map mLStringIndex; + + void getRecordDataPostActions(); // housekeeping actions before processing the next record + void buildLStringIndex(const std::string& stringFile, LocalizedStringType stringType); + + public: + + Reader(); + ~Reader(); + + // Methods added for updating loading progress bars + inline std::size_t getFileSize() const { return mStream->size(); } + inline std::size_t getFileOffset() const { return mStream->tell(); } + + // Methods added for saving/restoring context + ReaderContext getContext(); + bool restoreContext(const ReaderContext& ctx); // returns the result of re-reading the header + bool skipNextGroupCellChild(); // returns true if skipped + + std::size_t openTes4File(const std::string& name); + + // NOTE: must be called before calling getRecordHeader() + void setRecHeaderSize(const std::size_t size); + + inline void loadHeader() { mHeader.load(*this); } + inline unsigned int esmVersion() const { return mHeader.mData.version.ui; } + inline unsigned int numRecords() const { return mHeader.mData.records; } + + void buildLStringIndex(); + inline bool hasLocalizedStrings() const { return (mHeader.mFlags & Rec_Localized) != 0; } + void getLocalizedString(std::string& str); // convenience method for below + void getLocalizedString(const FormId stringId, std::string& str); + + // Read 24 bytes of header. The caller can then decide whether to process or skip the data. + bool getRecordHeader(); + + inline const RecordHeader& hdr() const { return mRecordHeader; } + + const GroupTypeHeader& grp(std::size_t pos = 0) const; + + // The object setting up this reader needs to supply the file's load order index + // so that the formId's in this file can be adjusted with the file (i.e. mod) index. + void setModIndex(int index) { mCtx.modIndex = (index << 24) & 0xff000000; } + void updateModIndicies(const std::vector& files); + + // Maybe should throw an exception if called when not valid? + const CellGrid& currCellGrid() const; + + inline const bool hasCellGrid() const { return mCellGridValid; } + + // This is set while loading a CELL record (XCLC sub record) and invalidated + // each time loading a CELL (see clearCellGrid()) + inline void setCurrCellGrid(const CellGrid& currCell) { + mCellGridValid = true; + mCurrCellGrid = currCell; + } + + // FIXME: This is called each time a new CELL record is read. Rather than calling this + // methos explicitly, mCellGridValid should be set automatically somehow. + // + // Cell 2c143 is loaded immedicatly after 1bdb1 and can mistakely appear to have grid 0, 1. + inline void clearCellGrid() { mCellGridValid = false; } + + // Should be set at the beginning of a CELL load + inline void setCurrCell(FormId formId) { mCtx.currCell = formId; } + + inline FormId currCell() const { return mCtx.currCell; } + + // Should be set at the beginning of a WRLD load + inline void setCurrWorld(FormId formId) { mCtx.currWorld = formId; } + + inline FormId currWorld() const { return mCtx.currWorld; } + + // Get the data part of a record + // Note: assumes the header was read correctly and nothing else was read + void getRecordData(); + + // Skip the data part of a record + // Note: assumes the header was read correctly and nothing else was read + void skipRecordData(); + + // Skip the rest of the group + // Note: assumes the header was read correctly and nothing else was read + void skipGroup(); + + // Read 6 bytes of header. The caller can then decide whether to process or skip the data. + bool getSubRecordHeader(); + + // Manally update (i.e. reduce) the bytes remaining to be read after SUB_XXXX + inline void updateRecordRemaining(std::uint32_t subSize) { mRecordRemaining -= subSize; } + + inline const SubRecordHeader& subRecordHeader() const { return mSubRecordHeader; } + + // Skip the data part of a subrecord + // Note: assumes the header was read correctly and nothing else was read + void skipSubRecordData(); + + // Special for a subrecord following a XXXX subrecord + void skipSubRecordData(std::uint32_t size); + + // Get a subrecord of a particular type and data type + template + bool getSubRecord(const ESM4::SubRecordTypes type, T& t) + { + ESM4::SubRecordHeader hdr; + if (!get(hdr) || (hdr.typeId != type) || (hdr.dataSize != sizeof(T))) + return false; + + return get(t); + } + + template + inline bool get(T& t) { + return mStream->read(&t, sizeof(T)) == sizeof(T); // FIXME: try/catch block needed? + } + + // for arrays + inline bool get(void* p, std::size_t size) { + return mStream->read(p, size) == size; // FIXME: try/catch block needed? + } + + // ModIndex adjusted formId according to master file dependencies + void adjustFormId(FormId& id); + + bool getFormId(FormId& id); + + void adjustGRUPFormId(); + + // Note: does not convert to UTF8 + // Note: assumes string size from the subrecord header + bool getZString(std::string& str); + bool getZString(std::string& str, Ogre::DataStreamPtr fileStream); + + void checkGroupStatus(); + + void saveGroupStatus(); + + void registerForUpdates(ReaderObserver *observer); + + // for debugging only + size_t stackSize() const { return mCtx.groupStack.size(); } + }; + + // An idea on extending the 254 mods limit + // --------------------------------------- + // + // By using a 64bit formid internally it should be possible to extend the limit. However + // saved games won't be compatible. + // + // One or two digits can be used, which will allow 4096-2=4094 or 65535-2=65533 mods. + // With the remaining digits one can be used as a game index (e.g. TES3=0, TES4=1, etc). + // + // The remaining bits might still be useful for indicating something else about the object. + // + // game index + // | + // | mod index extend to 4 digits (or 3 digits?) + // | +---+ + // | | | + // v v v + // 0xfffff f ff ff ffffff + // ^^ ^ ^ + // || | | + // || +----+ + // || 6 digit obj index + // ++ + // 2 digit mod index + // +} + +#endif // ESM4_READER_H diff --git a/extern/esm4/refr.cpp b/extern/esm4/refr.cpp new file mode 100644 index 000000000..42660fbb6 --- /dev/null +++ b/extern/esm4/refr.cpp @@ -0,0 +1,269 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "refr.hpp" + +#include + +#include // FIXME: debug only +#include "formid.hpp" // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Reference::Reference() : mFormId(0), mFlags(0), mDisabled(false), mBaseObj(0), mScale(1.f), + mOwner(0), mGlobal(0), mFactionRank(0), mCount(1) +{ + mEditorId.clear(); + mFullName.clear(); + + mEsp.parent = 0; + mEsp.flags = 0; + + mDoor.destDoor = 0; +} + +ESM4::Reference::~Reference() +{ +} + +void ESM4::Reference::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + // TODO: Let the engine apply this? Saved games? + //mDisabled = ((mFlags & ESM4::Rec_Disabled) != 0) ? true : false; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM4::VER_132 || esmVer == ESM4::VER_133 || esmVer == ESM4::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("REFR FULL data read error"); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "REFR Full Name: " << mFullName << std::endl; +#endif + break; + } + case ESM4::SUB_NAME: + { + reader.getFormId(mBaseObj); +#if 0 + if (mFlags & ESM4::Rec_Disabled) + std::cout << "REFR disable at start " << formIdToString(mFormId) << + " baseobj " << formIdToString(mBaseObj) << + " " << (mEditorId.empty() ? "" : mEditorId) << std::endl; // FIXME +#endif + break; + } + case ESM4::SUB_DATA: reader.get(mPosition); break; + case ESM4::SUB_XSCL: reader.get(mScale); break; + case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; + case ESM4::SUB_XGLB: reader.getFormId(mGlobal); break; + case ESM4::SUB_XRNK: reader.get(mFactionRank); break; + case ESM4::SUB_XESP: + { + reader.get(mEsp); + reader.adjustFormId(mEsp.parent); + //std::cout << "REFR parent: " << formIdToString(mEsp.parent) << " ref " << formIdToString(mFormId) + //<< ", 0x" << std::hex << (mEsp.flags & 0xff) << std::endl;// FIXME + break; + } + case ESM4::SUB_XTEL: + { + reader.get(mDoor.destDoor); + reader.get(mDoor.destPos); + if (esmVer == ESM4::VER_094 || esmVer == ESM4::VER_170 || isFONV) + reader.get(mDoor.flags); // not in Obvlivion + //std::cout << "REFR dest door: " << formIdToString(mDoor.destDoor) << std::endl;// FIXME + break; + } + case ESM4::SUB_XSED: + { + // 1 or 4 bytes + if (subHdr.dataSize == 1) + { + uint8_t data = reader.get(data); + //std::cout << "REFR XSED " << std::hex << (int)data << std::endl; + break; + } + else if (subHdr.dataSize == 4) + { + uint32_t data = reader.get(data); + //std::cout << "REFR XSED " << std::hex << (int)data << std::endl; + break; + } + + //std::cout << "REFR XSED dataSize: " << subHdr.dataSize << std::endl;// FIXME + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XLOD: + { + // 12 bytes + if (subHdr.dataSize == 12) + { + uint32_t data = reader.get(data); + uint32_t data2 = reader.get(data); + uint32_t data3 = reader.get(data); + //std::cout << "REFR XLOD " << std::hex << (int)data << " " << (int)data2 << " " << (int)data3 << std::endl; + break; + } + //std::cout << "REFR XLOD dataSize: " << subHdr.dataSize << std::endl;// FIXME + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XACT: + { + if (subHdr.dataSize == 4) + { + uint32_t data = reader.get(data); + //std::cout << "REFR XACT " << std::hex << (int)data << std::endl; + break; + } + + //std::cout << "REFR XACT dataSize: " << subHdr.dataSize << std::endl;// FIXME + reader.skipSubRecordData(); + break; + } + // seems like another ref, e.g. 00064583 has base object 00000034 which is "XMarkerHeading" + case ESM4::SUB_XRTM: // formId + { + FormId id; + reader.get(id); + //std::cout << "REFR XRTM : " << formIdToString(id) << std::endl;// FIXME + break; + } + // lighting + case ESM4::SUB_LNAM: // lighting template formId + case ESM4::SUB_XLIG: // struct, FOV, fade, etc + case ESM4::SUB_XEMI: // LIGH formId + case ESM4::SUB_XRDS: // Radius or Radiance + case ESM4::SUB_XRGB: + case ESM4::SUB_XRGD: // tangent data? + case ESM4::SUB_XALP: // alpha cutoff + // + case ESM4::SUB_XLOC: // formId + case ESM4::SUB_XMRK: + case ESM4::SUB_FNAM: + case ESM4::SUB_XTRG: // formId + case ESM4::SUB_XPCI: // formId + case ESM4::SUB_XLCM: + case ESM4::SUB_XCNT: + case ESM4::SUB_TNAM: + case ESM4::SUB_ONAM: + case ESM4::SUB_VMAD: + case ESM4::SUB_XPRM: + case ESM4::SUB_INAM: + case ESM4::SUB_PDTO: + case ESM4::SUB_SCHR: + case ESM4::SUB_SCTX: + case ESM4::SUB_XAPD: + case ESM4::SUB_XAPR: + case ESM4::SUB_XCVL: + case ESM4::SUB_XCZA: + case ESM4::SUB_XCZC: + case ESM4::SUB_XEZN: + case ESM4::SUB_XFVC: + case ESM4::SUB_XHTW: + case ESM4::SUB_XIS2: + case ESM4::SUB_XLCN: + case ESM4::SUB_XLIB: + case ESM4::SUB_XLKR: + case ESM4::SUB_XLRM: + case ESM4::SUB_XLRT: + case ESM4::SUB_XLTW: + case ESM4::SUB_XMBO: + case ESM4::SUB_XMBP: + case ESM4::SUB_XMBR: + case ESM4::SUB_XNDP: + case ESM4::SUB_XOCP: + case ESM4::SUB_XPOD: + case ESM4::SUB_XPPA: + case ESM4::SUB_XPRD: + case ESM4::SUB_XPWR: + case ESM4::SUB_XRMR: + case ESM4::SUB_XSPC: + case ESM4::SUB_XTNM: + case ESM4::SUB_XTRI: + case ESM4::SUB_XWCN: + case ESM4::SUB_XWCU: + case ESM4::SUB_XATR: // Dawnguard only? + case ESM4::SUB_XHLT: // Unofficial Oblivion Patch + case ESM4::SUB_XCHG: // thievery.exp + case ESM4::SUB_XHLP: // FO3 + case ESM4::SUB_XRDO: // FO3 + case ESM4::SUB_XAMT: // FO3 + case ESM4::SUB_XAMC: // FO3 + case ESM4::SUB_XRAD: // FO3 + case ESM4::SUB_XIBS: // FO3 + case ESM4::SUB_XORD: // FO3 + case ESM4::SUB_XCLP: // FO3 + case ESM4::SUB_SCDA: // FO3 + case ESM4::SUB_SCRO: // FO3 + case ESM4::SUB_RCLR: // FO3 + case ESM4::SUB_BNAM: // FONV + case ESM4::SUB_CNAM: // FONV + case ESM4::SUB_MMRK: // FONV + case ESM4::SUB_MNAM: // FONV + case ESM4::SUB_NNAM: // FONV + case ESM4::SUB_XATO: // FONV + case ESM4::SUB_SCRV: // FONV + case ESM4::SUB_SCVR: // FONV + case ESM4::SUB_SLSD: // FONV + case ESM4::SUB_XSRF: // FONV + case ESM4::SUB_XSRD: // FONV + case ESM4::SUB_WMI1: // FONV + case ESM4::SUB_XLRL: // Unofficial Skyrim Patch + { + //std::cout << "REFR " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::REFR::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Reference::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::Reference::blank() +{ +} diff --git a/extern/esm4/refr.hpp b/extern/esm4/refr.hpp new file mode 100644 index 000000000..1cef1abe7 --- /dev/null +++ b/extern/esm4/refr.hpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_REFR_H +#define ESM4_REFR_H + +#include "common.hpp" // Position + +namespace ESM4 +{ + class Reader; + class Writer; + + struct TeleportDest + { + FormId destDoor; + Position destPos; + std::uint32_t flags; // 0x01 no alarm (only in TES5) + }; + + // Unlike TES3, multiple cells can have the same exterior co-ordinates. + // The cells need to be organised under world spaces. + struct Reference + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + FormId mBaseObj; + + Position mPosition; + float mScale; // default 1.f + FormId mOwner; + FormId mGlobal; + std::uint32_t mFactionRank; + + bool mDisabled; + EnableParent mEsp; // TODO may need to check mFlags & 0x800 (initially disabled) + + std::uint32_t mCount; // only if > 1 (default 1) + + TeleportDest mDoor; + + Reference(); + virtual ~Reference(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; +} + +#endif // ESM4_REFR_H diff --git a/extern/esm4/regn.cpp b/extern/esm4/regn.cpp new file mode 100644 index 000000000..06826b13f --- /dev/null +++ b/extern/esm4/regn.cpp @@ -0,0 +1,152 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "regn.hpp" + +#include +#include + +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +ESM4::Region::Region() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mShader.clear(); + mMapName.clear(); + //mData.unknown = 1; // FIXME: temp use to indicate not loaded + mData.resize(8); +} + +ESM4::Region::~Region() +{ +} + +void ESM4::Region::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + RDAT_Types next = RDAT_None; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_RCLR: reader.get(mColour); break; + case ESM4::SUB_WNAM: reader.getFormId(mWorldId); break; + case ESM4::SUB_ICON: reader.getZString(mShader); break; + case ESM4::SUB_RPLI: reader.get(mEdgeFalloff); break; + case ESM4::SUB_RPLD: + { + mRPLD.resize(subHdr.dataSize/sizeof(std::uint32_t)); + for (std::vector::iterator it = mRPLD.begin(); it != mRPLD.end(); ++it) + { + reader.get(*it); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "RPLD: 0x" << std::hex << *it << std::endl; +#endif + } + break; + } + case ESM4::SUB_RDAT: + { + RDAT rdat; + reader.get(rdat); + + next = static_cast(rdat.type); + + mData[rdat.type].type = rdat.type; + mData[rdat.type].flag = rdat.flag; + mData[rdat.type].priority = rdat.priority; + mData[rdat.type].unknown = rdat.unknown; + + break; + } + case ESM4::SUB_RDMP: + { + assert(next == RDAT_Map && "REGN unexpected data type"); + next = RDAT_None; + + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mMapName); + else if (!reader.getZString(mMapName)) + throw std::runtime_error ("REGN RDMP data read error"); + break; + } + case ESM4::SUB_RDMD: // Only in Oblivion? + case ESM4::SUB_RDSD: // Only in Oblivion? Possibly the same as RDSA // formId + case ESM4::SUB_RDGS: // Only in Oblivion? (ToddTestRegion1) // formId + case ESM4::SUB_RDMO: + case ESM4::SUB_RDSA: + case ESM4::SUB_RDWT: // formId + case ESM4::SUB_RDOT: // formId + case ESM4::SUB_RDID: // FONV + case ESM4::SUB_RDSB: // FONV + case ESM4::SUB_RDSI: // FONV + { + //RDAT skipping... following is a map + //RDMP skipping... map name + // + //RDAT skipping... following is weather + //RDWT skipping... weather data + // + //RDAT skipping... following is sound + //RDMD skipping... unknown, maybe music data + // + //RDSD skipping... unknown, maybe sound data + // + //RDAT skipping... following is grass + //RDGS skipping... unknown, maybe grass + + //std::cout << "REGN " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + break; + } + default: + throw std::runtime_error("ESM4::REGN::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Region::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::Region::blank() +{ +} diff --git a/extern/esm4/regn.hpp b/extern/esm4/regn.hpp new file mode 100644 index 000000000..fdb1e5135 --- /dev/null +++ b/extern/esm4/regn.hpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_REGN_H +#define ESM4_REGN_H + +#include +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Region + { + enum RDAT_Types + { + RDAT_None = 0x00, + RDAT_Objects = 0x02, + RDAT_Weather = 0x03, + RDAT_Map = 0x04, + RDAT_Landscape = 0x05, + RDAT_Grass = 0x06, + RDAT_Sound = 0x07 + }; + + struct RDAT + { + std::uint32_t type; + std::uint8_t flag; + std::uint8_t priority; + std::uint16_t unknown; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::uint32_t mColour; // RGBA + FormId mWorldId; // worldspace formid + + std::string mShader; //?? ICON + std::string mMapName; + std::uint32_t mEdgeFalloff; + std::vector mRPLD; // unknown + std::vector mData; // indexed by the type value + + Region(); + virtual ~Region(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; +} + +#endif // ESM4_REGN_H diff --git a/extern/esm4/sbsp.cpp b/extern/esm4/sbsp.cpp new file mode 100644 index 000000000..4cee5c893 --- /dev/null +++ b/extern/esm4/sbsp.cpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "sbsp.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Subspace::Subspace() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + + mDimension.x = 0.f; + mDimension.y = 0.f; + mDimension.z = 0.f; +} + +ESM4::Subspace::~Subspace() +{ +} + +void ESM4::Subspace::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_DNAM: + { + reader.get(mDimension.x); + reader.get(mDimension.y); + reader.get(mDimension.z); + break; + } + default: + throw std::runtime_error("ESM4::SBSP::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Subspace::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Subspace::blank() +//{ +//} diff --git a/extern/esm4/sbsp.hpp b/extern/esm4/sbsp.hpp new file mode 100644 index 000000000..9962d30fa --- /dev/null +++ b/extern/esm4/sbsp.hpp @@ -0,0 +1,63 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SBSP_H +#define ESM4_SBSP_H + +#include +#include + +namespace ESM4 +{ + class Reader; + typedef std::uint32_t FormId; + + struct Subspace + { + struct Dimension + { + float x; + float y; + float z; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + Dimension mDimension; + + Subspace(); + virtual ~Subspace(); + + virtual void load(Reader& reader); + //virtual void save(Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SBSP_H diff --git a/extern/esm4/sgst.cpp b/extern/esm4/sgst.cpp new file mode 100644 index 000000000..022a4f984 --- /dev/null +++ b/extern/esm4/sgst.cpp @@ -0,0 +1,118 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "sgst.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::SigilStone::SigilStone() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScript(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.uses = 0; + mData.value = 0; + mData.weight = 0.f; + + std::memset(&mEffect, 0, sizeof(ScriptEffect)); +} + +ESM4::SigilStone::~SigilStone() +{ +} + +void ESM4::SigilStone::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (mFullName.empty()) + { + if (!reader.getZString(mFullName)) + throw std::runtime_error ("SGST FULL data read error"); + } + else + { + // FIXME: should be part of a struct? + std::string scriptEffectName; + if (!reader.getZString(scriptEffectName)) + throw std::runtime_error ("SGST FULL data read error"); + mScriptEffect.push_back(scriptEffectName); + } + break; + } + case ESM4::SUB_DATA: + { + reader.get(mData.uses); + reader.get(mData.value); + reader.get(mData.weight); + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_SCIT: + { + reader.get(mEffect); + reader.adjustFormId(mEffect.formId); + break; + } + case ESM4::SUB_MODT: + case ESM4::SUB_EFID: + case ESM4::SUB_EFIT: + { + //std::cout << "SGST " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SGST::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::SigilStone::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::SigilStone::blank() +//{ +//} diff --git a/extern/esm4/sgst.hpp b/extern/esm4/sgst.hpp new file mode 100644 index 000000000..be5243429 --- /dev/null +++ b/extern/esm4/sgst.hpp @@ -0,0 +1,74 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SGST_H +#define ESM4_SGST_H + +#include + +#include "common.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct SigilStone + { + struct Data + { + std::uint8_t uses; + std::uint32_t value; // gold + float weight; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + std::vector mScriptEffect; // FIXME: prob. should be in a struct + FormId mScript; + ScriptEffect mEffect; + + Data mData; + + SigilStone(); + virtual ~SigilStone(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SGST_H diff --git a/extern/esm4/slgm.cpp b/extern/esm4/slgm.cpp new file mode 100644 index 000000000..47265c217 --- /dev/null +++ b/extern/esm4/slgm.cpp @@ -0,0 +1,99 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "slgm.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::SoulGem::SoulGem() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScript(0), mSoul(0), mSoulCapacity(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::SoulGem::~SoulGem() +{ +} + +void ESM4::SoulGem::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("SLGM FULL data read error"); + + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_SOUL: reader.get(mSoul); break; + case ESM4::SUB_SLCP: reader.get(mSoulCapacity); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_NAM0: + case ESM4::SUB_OBND: + { + //std::cout << "SLGM " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SLGM::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::SoulGem::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::SoulGem::blank() +//{ +//} diff --git a/extern/esm4/slgm.hpp b/extern/esm4/slgm.hpp new file mode 100644 index 000000000..75261c068 --- /dev/null +++ b/extern/esm4/slgm.hpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SLGM_H +#define ESM4_SLGM_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct SoulGem + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + FormId mScript; + std::uint8_t mSoul; // 0 = None, 1 = Petty, 2 = Lesser, 3 = Common, 4 = Greater, 5 = Grand + std::uint8_t mSoulCapacity; // 0 = None, 1 = Petty, 2 = Lesser, 3 = Common, 4 = Greater, 5 = Grand + + Data mData; + + SoulGem(); + virtual ~SoulGem(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SLGM_H diff --git a/extern/esm4/soun.cpp b/extern/esm4/soun.cpp new file mode 100644 index 000000000..02d0ff556 --- /dev/null +++ b/extern/esm4/soun.cpp @@ -0,0 +1,82 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "soun.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Sound::Sound() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mSoundFile.clear(); +} + +ESM4::Sound::~Sound() +{ +} + +void ESM4::Sound::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FNAM: reader.getZString(mSoundFile); break; + case ESM4::SUB_SNDX: reader.get(mData); break; + case ESM4::SUB_SNDD: + case ESM4::SUB_OBND: // TES5 only + case ESM4::SUB_SDSC: // TES5 only + case ESM4::SUB_ANAM: // FO3 + case ESM4::SUB_GNAM: // FO3 + case ESM4::SUB_HNAM: // FO3 + case ESM4::SUB_RNAM: // FONV + { + //std::cout << "SOUN " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SOUN::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Sound::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Sound::blank() +//{ +//} diff --git a/extern/esm4/soun.hpp b/extern/esm4/soun.hpp new file mode 100644 index 000000000..388a439aa --- /dev/null +++ b/extern/esm4/soun.hpp @@ -0,0 +1,86 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SOUN_H +#define ESM4_SOUN_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Sound + { + enum Flags + { + Flags_RandomFreqShift = 0x0001, + Flags_PlayAtRandom = 0x0002, + Flags_EnvIgnored = 0x0004, + Flags_RandomLocation = 0x0008, + Flags_Loop = 0x0010, + Flags_MenuSound = 0x0020, + Flags_2D = 0x0040, + Flags_360LFE = 0x0080 + }; + +#pragma pack(push, 1) + struct SNDX + { + std::uint8_t minAttenuation; + std::uint8_t maxAttenuation; + std::int8_t freqAdjustment; // %, signed + std::uint8_t unknown; + std::uint16_t flags; + std::uint16_t unknown2; + std::uint16_t staticAttenuation; // divide by 100 to get value in dB + std::uint8_t stopTime; // multipy vy 1440/256 to get value in minutes + std::uint8_t startTime; // multipy vy 1440/256 to get value in minutes + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::string mSoundFile; + SNDX mData; + + Sound(); + virtual ~Sound(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SOUN_H diff --git a/extern/esm4/stat.cpp b/extern/esm4/stat.cpp new file mode 100644 index 000000000..850b6f504 --- /dev/null +++ b/extern/esm4/stat.cpp @@ -0,0 +1,101 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "stat.hpp" + +#include +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Static::Static() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::Static::~Static() +{ +} + +void ESM4::Static::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + { + // version is only availabe in TES5 (seems to be 27 or 28?) + //if (reader.esmVersion() == ESM4::VER_094 || reader.esmVersion() == ESM4::VER_170) + //std::cout << "STAT MODT ver: " << std::hex << reader.hdr().record.version << std::endl; + + // for TES4 these are just a sequence of bytes + mMODT.resize(subHdr.dataSize/sizeof(std::uint8_t)); + for (std::vector::iterator it = mMODT.begin(); it != mMODT.end(); ++it) + { + reader.get(*it); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "MODT: " << std::hex << *it << std::endl; +#endif + } + break; + } + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_DNAM: + case ESM4::SUB_MNAM: + case ESM4::SUB_BRUS: // FONV + case ESM4::SUB_RNAM: // FONV + { + //std::cout << "STAT " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::STAT::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Static::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Static::blank() +//{ +//} diff --git a/extern/esm4/stat.hpp b/extern/esm4/stat.hpp new file mode 100644 index 000000000..13f93141f --- /dev/null +++ b/extern/esm4/stat.hpp @@ -0,0 +1,63 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_STAT_H +#define ESM4_STAT_H + +#include +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + // Unlike TES3, multiple cells can have the same exterior co-ordinates. + // The cells need to be organised under world spaces. + struct Static + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + std::vector mMODT; // FIXME texture hash + + Static(); + virtual ~Static(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_STAT_H diff --git a/extern/esm4/tes4.cpp b/extern/esm4/tes4.cpp new file mode 100644 index 000000000..59e369d89 --- /dev/null +++ b/extern/esm4/tes4.cpp @@ -0,0 +1,123 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "tes4.hpp" + +#include +#include + +#include // FIXME: debugging only + +#include "common.hpp" +#include "formid.hpp" +#include "reader.hpp" +//#include "writer.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +void ESM4::Header::load(ESM4::Reader& reader) +{ + mFlags = reader.hdr().record.flags; // 0x01 = Rec_ESM, 0x80 = Rec_Localized + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_HEDR: + { + if (!reader.get(mData.version) || !reader.get(mData.records) || !reader.get(mData.nextObjectId)) + throw std::runtime_error("TES4 HEDR data read error"); + + assert((size_t)subHdr.dataSize == sizeof(mData.version)+sizeof(mData.records)+sizeof(mData.nextObjectId) + && "TES4 HEDR data size mismatch"); + break; + } + case ESM4::SUB_CNAM: + { + if (!reader.getZString(mAuthor)) + throw std::runtime_error("TES4 CNAM data read error"); + break; + } + case ESM4::SUB_SNAM: + { + if (!reader.getZString(mDesc)) + throw std::runtime_error("TES4 SNAM data read error"); + break; + } + case ESM4::SUB_MAST: // multiple + { + MasterData m; + if (!reader.getZString(m.name)) + throw std::runtime_error("TES4 MAST data read error"); + + // NOTE: some mods do not have DATA following MAST so can't read DATA here + + mMaster.push_back (m); + break; + } + case ESM4::SUB_DATA: + { + // WARNING: assumes DATA always follows MAST + if (!reader.get(mMaster.back().size)) + throw std::runtime_error("TES4 DATA data read error"); + break; + } + case ESM4::SUB_ONAM: + { + mOverrides.resize(subHdr.dataSize/sizeof(FormId)); + for (std::vector::iterator it = mOverrides.begin(); it != mOverrides.end(); ++it) + { + if (!reader.get(*it)) + throw std::runtime_error("TES4 ONAM data read error"); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "ESM4::Header::ONAM overrides: " << formIdToString(*it) << std::endl; +#endif + } + break; + } + case ESM4::SUB_INTV: + case ESM4::SUB_INCC: + case ESM4::SUB_OFST: // Oblivion only? + case ESM4::SUB_DELE: // Oblivion only? + { + //std::cout << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::Header::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Header::save(ESM4::Writer& writer) +//{ +//} diff --git a/extern/esm4/tes4.hpp b/extern/esm4/tes4.hpp new file mode 100644 index 000000000..15e164366 --- /dev/null +++ b/extern/esm4/tes4.hpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TES4_H +#define ESM4_TES4_H + +#include +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Header + { +#pragma pack(push, 1) + union ESMVersion + { + float f; + unsigned int ui; + }; + + struct Data + { + // The supported versions are 0.80 = 0x3f800000, 0.94 = 0x3f70a3d7 and 1.7 = 0x3fd9999a + // (also 1.34 = 0x3fab851f eventually) + ESMVersion version; // File format version. + std::int32_t records; // Number of records + std::uint32_t nextObjectId; + }; +#pragma pack(pop) + + // Defines another files (esm or esp) that this file depends upon. + struct MasterData + { + std::string name; + std::uint64_t size; + }; + + std::uint32_t mFlags; // 0x01 esm, 0x80 localised strings + + Data mData; + std::string mAuthor; // Author's name + std::string mDesc; // File description + std::vector mMaster; + + std::vector mOverrides; // Skyrim only, cell children (ACHR, LAND, NAVM, PGRE, PHZD, REFR) + + // position in the vector = mod index of master files above + // value = adjusted mod index based on all the files loaded so far + std::vector mModIndicies; + + void load (Reader& reader); + //void save (Writer& writer); + }; +} + +#endif // ESM4_TES4_H diff --git a/extern/esm4/tree.cpp b/extern/esm4/tree.cpp new file mode 100644 index 000000000..ea28b188e --- /dev/null +++ b/extern/esm4/tree.cpp @@ -0,0 +1,85 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "tree.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Tree::Tree() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mModel.clear(); + mLeafTexture.clear(); +} + +ESM4::Tree::~Tree() +{ +} + +void ESM4::Tree::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mLeafTexture); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_CNAM: + case ESM4::SUB_BNAM: + case ESM4::SUB_SNAM: + case ESM4::SUB_FULL: + case ESM4::SUB_OBND: + case ESM4::SUB_PFIG: + case ESM4::SUB_PFPC: + { + //std::cout << "TREE " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::TREE::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Tree::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Tree::blank() +//{ +//} diff --git a/extern/esm4/tree.hpp b/extern/esm4/tree.hpp new file mode 100644 index 000000000..b7213eacb --- /dev/null +++ b/extern/esm4/tree.hpp @@ -0,0 +1,61 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TREE_H +#define ESM4_TREE_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Tree + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + + std::string mLeafTexture; + + Tree(); + virtual ~Tree(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_TREE_H diff --git a/extern/esm4/weap.cpp b/extern/esm4/weap.cpp new file mode 100644 index 000000000..f9fd4d8cd --- /dev/null +++ b/extern/esm4/weap.cpp @@ -0,0 +1,194 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "weap.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Weapon::Weapon() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScript(0), + mEnchantmentPoints(0), mEnchantment(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); +} + +ESM4::Weapon::~Weapon() +{ +} + +void ESM4::Weapon::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM4::VER_132 || esmVer == ESM4::VER_133 || esmVer == ESM4::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("WEAP FULL data read error"); + + break; + } + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM4::VER_094 || reader.esmVersion() == ESM4::VER_170) + if (subHdr.dataSize == 10) // FO3 has 15 bytes even though VER_094 + { + reader.get(mData.value); + reader.get(mData.weight); + reader.get(mData.damage); + } + else if (isFONV || subHdr.dataSize == 15) + { + reader.get(mData.value); + reader.get(mData.health); + reader.get(mData.weight); + reader.get(mData.damage); + reader.get(mData.clipSize); + } + else + { + reader.get(mData.type); + reader.get(mData.speed); + reader.get(mData.reach); + reader.get(mData.flags); + reader.get(mData.value); + reader.get(mData.health); + reader.get(mData.weight); + reader.get(mData.damage); + } + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_SCRI: reader.getFormId(mScript); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_BAMT: + case ESM4::SUB_BIDS: + case ESM4::SUB_INAM: + case ESM4::SUB_CNAM: + case ESM4::SUB_CRDT: + case ESM4::SUB_DESC: + case ESM4::SUB_DNAM: + case ESM4::SUB_EAMT: + case ESM4::SUB_EITM: + case ESM4::SUB_ETYP: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_NAM8: + case ESM4::SUB_NAM9: + case ESM4::SUB_OBND: + case ESM4::SUB_SNAM: + case ESM4::SUB_TNAM: + case ESM4::SUB_UNAM: + case ESM4::SUB_VMAD: + case ESM4::SUB_VNAM: + case ESM4::SUB_WNAM: + case ESM4::SUB_XNAM: // Dawnguard only? + case ESM4::SUB_NNAM: + case ESM4::SUB_MODS: + case ESM4::SUB_NAM0: // FO3 + case ESM4::SUB_MICO: // FO3 + case ESM4::SUB_REPL: // FO3 + case ESM4::SUB_YNAM: // FO3 + case ESM4::SUB_ZNAM: // FO3 + case ESM4::SUB_MOD2: // FO3 + case ESM4::SUB_MO2T: // FO3 + case ESM4::SUB_MO2S: // FO3 + case ESM4::SUB_NAM6: // FO3 + case ESM4::SUB_MOD4: // FO3 + case ESM4::SUB_MO4T: // FO3 + case ESM4::SUB_MO4S: // FO3 + case ESM4::SUB_BIPL: // FO3 + case ESM4::SUB_NAM7: // FO3 + case ESM4::SUB_MOD3: // FO3 + case ESM4::SUB_MO3T: // FO3 + case ESM4::SUB_MO3S: // FO3 + case ESM4::SUB_MODD: // FO3 + //case ESM4::SUB_MOSD: // FO3 + case ESM4::SUB_DEST: // FO3 + case ESM4::SUB_DSTD: // FO3 + case ESM4::SUB_DSTF: // FO3 + case ESM4::SUB_DMDL: // FO3 + case ESM4::SUB_DMDT: // FO3 + case ESM4::SUB_VATS: // FONV + case ESM4::SUB_VANM: // FONV + case ESM4::SUB_MWD1: // FONV + case ESM4::SUB_MWD2: // FONV + case ESM4::SUB_MWD3: // FONV + case ESM4::SUB_MWD4: // FONV + case ESM4::SUB_MWD5: // FONV + case ESM4::SUB_MWD6: // FONV + case ESM4::SUB_MWD7: // FONV + case ESM4::SUB_WMI1: // FONV + case ESM4::SUB_WMI2: // FONV + case ESM4::SUB_WMI3: // FONV + case ESM4::SUB_WMS1: // FONV + case ESM4::SUB_WMS2: // FONV + case ESM4::SUB_WNM1: // FONV + case ESM4::SUB_WNM2: // FONV + case ESM4::SUB_WNM3: // FONV + case ESM4::SUB_WNM4: // FONV + case ESM4::SUB_WNM5: // FONV + case ESM4::SUB_WNM6: // FONV + case ESM4::SUB_WNM7: // FONV + case MKTAG('E','F','S','D'): // FONV DeadMoney + { + //std::cout << "WEAP " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::WEAP::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Weapon::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Weapon::blank() +//{ +//} diff --git a/extern/esm4/weap.hpp b/extern/esm4/weap.hpp new file mode 100644 index 000000000..ea8d5eefb --- /dev/null +++ b/extern/esm4/weap.hpp @@ -0,0 +1,83 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_WEAP_H +#define ESM4_WEAP_H + +#include +#include + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Weapon + { + struct Data + { + std::uint32_t type; + float speed; + float reach; + std::uint32_t flags; + std::uint32_t value; // gold + std::uint32_t health; + float weight; + std::uint16_t damage; + std::uint8_t clipSize; // FO3/FONV only + + Data() : type(0), speed(0.f), reach(0.f), flags(0), value(0), + health(0), weight(0.f), damage(0), clipSize(0) {} + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; + + float mBoundRadius; + + FormId mScript; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + Weapon(); + virtual ~Weapon(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_WEAP_H diff --git a/extern/esm4/wrld.cpp b/extern/esm4/wrld.cpp new file mode 100644 index 000000000..3826e32ad --- /dev/null +++ b/extern/esm4/wrld.cpp @@ -0,0 +1,179 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "wrld.hpp" + +#include +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::World::World() : mFormId(0), mFlags(0), mParent(0), mWorldFlags(0), mClimate(0), mWater(0), + mMinX(0), mMinY(0), mMaxX(0), mMaxY(0), mSound(0) +{ + mEditorId.clear(); + mFullName.clear(); + mMapFile.clear(); + + mMap.width = 0; + mMap.height = 0; + mMap.NWcellX = 0; + mMap.NWcellY = 0; + mMap.SEcellX = 0; + mMap.SEcellY = 0; + mMap.minHeight = 0.f; + mMap.maxHeight = 0.f; + mMap.initialPitch = 0.f; +} + +ESM4::World::~World() +{ +} + +void ESM4::World::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + // It should be possible to save the current world formId automatically while reading in + // the record header rather than doing it manually here but possibly less efficient (may + // need to check each record?). + // + // Alternatively it may be possible to figure it out by examining the group headers, but + // apparently the label field is not reliable so the parent world formid may have been + // corrupted by the use of ignore flag (TODO: should check to verify). + reader.setCurrWorld(mFormId); // save for CELL later + + std::uint32_t subSize = 0; // for XXXX sub record + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: // Name of the worldspace + { + if (reader.hasLocalizedStrings()) + reader.getLocalizedString(mFullName); + else if (!reader.getZString(mFullName)) + throw std::runtime_error ("WRLD FULL data read error"); + + break; + } + case ESM4::SUB_WCTR: reader.get(mCenterCell); break; // Center cell, TES5 only + case ESM4::SUB_WNAM: reader.getFormId(mParent); break; + case ESM4::SUB_SNAM: reader.get(mSound); break; // sound, Oblivion only? + case ESM4::SUB_ICON: reader.getZString(mMapFile); break; // map filename, Oblivion only? + case ESM4::SUB_CNAM: reader.get(mClimate); break; + case ESM4::SUB_NAM2: reader.getFormId(mWater); break; + case ESM4::SUB_NAM0: + { + reader.get(mMinX); + reader.get(mMinY); + break; + } + case ESM4::SUB_NAM9: + { + reader.get(mMaxX); + reader.get(mMaxY); + break; + } + case ESM4::SUB_DATA: reader.get(mWorldFlags); break; + case ESM4::SUB_MNAM: + { + reader.get(mMap.width); + reader.get(mMap.height); + reader.get(mMap.NWcellX); + reader.get(mMap.NWcellY); + reader.get(mMap.SEcellX); + reader.get(mMap.SEcellY); + + if (subHdr.dataSize == 28) // Skyrim? + { + reader.get(mMap.minHeight); + reader.get(mMap.maxHeight); + reader.get(mMap.initialPitch); + } + + break; + } + case ESM4::SUB_RNAM: // multiple + case ESM4::SUB_MHDT: + case ESM4::SUB_LTMP: + case ESM4::SUB_XEZN: + case ESM4::SUB_XLCN: + case ESM4::SUB_NAM3: + case ESM4::SUB_NAM4: + case ESM4::SUB_DNAM: + case ESM4::SUB_MODL: + case ESM4::SUB_NAMA: + case ESM4::SUB_PNAM: + case ESM4::SUB_ONAM: + case ESM4::SUB_TNAM: + case ESM4::SUB_UNAM: + case ESM4::SUB_ZNAM: + case ESM4::SUB_XWEM: + case ESM4::SUB_MODT: // from Dragonborn onwards? + case ESM4::SUB_INAM: // FO3 + case ESM4::SUB_NNAM: // FO3 + case ESM4::SUB_XNAM: // FO3 + case ESM4::SUB_IMPS: // FO3 Anchorage + case ESM4::SUB_IMPF: // FO3 Anchorage + { + //std::cout << "WRLD " << ESM4::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + break; + } + case ESM4::SUB_OFST: + { + if (subSize) + { + reader.skipSubRecordData(subSize); // special post XXXX + reader.updateRecordRemaining(subSize); // WARNING: manually update + subSize = 0; + } + else + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + + break; + } + case ESM4::SUB_XXXX: + { + reader.get(subSize); + break; + } + default: + throw std::runtime_error("ESM4::WRLD::load - Unknown subrecord " + ESM4::printName(subHdr.typeId)); + } + } +} + +//void ESM4::World::save(ESM4::Writer& writer) const +//{ +//} diff --git a/extern/esm4/wrld.hpp b/extern/esm4/wrld.hpp new file mode 100644 index 000000000..26eb23973 --- /dev/null +++ b/extern/esm4/wrld.hpp @@ -0,0 +1,122 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_WRLD_H +#define ESM4_WRLD_H + +#include + +#include "common.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct World + { + enum WorldFlags // TES4 TES5 + { // -------------------- ----------------- + WLD_Small = 0x01, // Small World Small World + WLD_NoFastTravel = 0x02, // Can't Fast Travel Can't Fast Travel + WLD_Oblivion = 0x04, // Oblivion worldspace + WLD_NoLODWater = 0x08, // No LOD Water + WLD_NoLandscpe = 0x10, // No LOD Water No Landscape + WLD_NoSky = 0x20, // No Sky + wLD_FixedDimension = 0x40, // Fixed Dimensions + WLD_NoGrass = 0x80 // No Grass + }; + + struct REFRcoord + { + FormId formId; + std::int16_t unknown1; + std::int16_t unknown2; + }; + + struct RNAMstruct + { + std::int16_t unknown1; + std::int16_t unknown2; + std::vector refrs; + }; + + //Map size struct 16 or 28 byte structure + struct Map + { + std::uint32_t width; // usable width of the map + std::uint32_t height; // usable height of the map + std::int16_t NWcellX; + std::int16_t NWcellY; + std::int16_t SEcellX; + std::int16_t SEcellY; + float minHeight; // Camera Data (default 50000), new as of Skyrim 1.8, purpose is not yet known. + float maxHeight; // Camera Data (default 80000) + float initialPitch; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + FormId mParent; // parent worldspace formid + std::uint8_t mWorldFlags; + FormId mClimate; + FormId mWater; + + Map mMap; + + std::int32_t mMinX; + std::int32_t mMinY; + std::int32_t mMaxX; + std::int32_t mMaxY; + + // ------ TES4 only ----- + + std::int32_t mSound; // 0 = no record, 1 = Public, 2 = Dungeon + std::string mMapFile; + + // ------ TES5 only ----- + + Grid mCenterCell; + RNAMstruct mData; + + // ---------------------- + + // cache formId's of children (e.g. CELL, ROAD) + std::vector mCells; + std::vector mRoads; + + World(); + virtual ~World(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + }; +} + +#endif // ESM4_WRLD_H From 6ec6b9bc2ac082b4ee1319d263dbc4acde726be5 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 7 Oct 2018 08:55:30 +1100 Subject: [PATCH 363/441] OpenMW Integration. Read the ESM/ESP records but do nothing with them for the moment. --- CMakeLists.txt | 1 + apps/openmw/CMakeLists.txt | 1 + apps/openmw/mwworld/esmloader.cpp | 19 +++- apps/openmw/mwworld/esmstore.cpp | 146 +++++++++++++++++++++++++++++- apps/openmw/mwworld/esmstore.hpp | 9 ++ components/CMakeLists.txt | 28 +++--- components/esm/esm4reader.cpp | 94 +++++++++++++++++++ components/esm/esm4reader.hpp | 38 ++++++++ 8 files changed, 319 insertions(+), 17 deletions(-) create mode 100644 components/esm/esm4reader.cpp create mode 100644 components/esm/esm4reader.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ec616cbb..ac5606a99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -580,6 +580,7 @@ add_subdirectory (extern/shiny) add_subdirectory (extern/ogre-ffmpeg-videoplayer) add_subdirectory (extern/oics) add_subdirectory (extern/sdl4ogre) +add_subdirectory (extern/esm4) add_subdirectory (extern/murmurhash) add_subdirectory (extern/BSAOpt) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index cdb1691f0..e7655b237 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -117,6 +117,7 @@ target_link_libraries(openmw ${OGRE_LIBRARIES} ${OGRE_STATIC_PLUGINS} ${SHINY_LIBRARIES} + ${ESM4_LIBRARIES} ${BSAOPTHASH_LIBRARIES} ${ZLIB_LIBRARY} ${OPENAL_LIBRARY} diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index e87ad8a04..ca1083c2f 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -2,6 +2,7 @@ #include "esmstore.hpp" #include +#include namespace MWWorld { @@ -42,8 +43,22 @@ void EsmLoader::load(const boost::filesystem::path& filepath, std::vectorclose(); + delete lEsm; + ESM::ESM4Reader *esm = new ESM::ESM4Reader(isTes4); // NOTE: TES4 headers are 4 bytes shorter + esm->setEncoder(mEncoder); + + index = contentFiles[tesVerIndex].size(); + contentFiles[tesVerIndex].push_back(filepath.filename().string()); + esm->setIndex(index); + + esm->reader().setModIndex(index); + esm->openTes4File(filepath.string()); + esm->reader().updateModIndicies(contentFiles[tesVerIndex]); + // FIXME: this does not work well (copies the base class pointer) + //i.e. have to check TES4/TES5 versions each time before use within EsmStore::load, + //static casting as required + mEsm[tesVerIndex].push_back(esm); } else { diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 1b05cb19b..74230bbf6 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include namespace MWWorld { @@ -82,6 +84,16 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) // Loop through all records while(esm.hasMoreRecs()) { + if (isTes4 || isTes5 || isFONV) + { + ESM4::Reader& reader = static_cast(&esm)->reader(); + reader.checkGroupStatus(); + + loadTes4Group(esm); + listener->setProgress(static_cast(esm.getFileOffset() / (float)esm.getFileSize() * 1000)); + continue; + } + ESM::NAME n = esm.getRecName(); esm.getRecHeader(); @@ -136,6 +148,138 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) } } +// Can't use ESM4::Reader& as the parameter here because we need esm.hasMoreRecs() for +// checking an empty group followed by EOF +void ESMStore::loadTes4Group (ESM::ESMReader &esm) +{ + ESM4::Reader& reader = static_cast(&esm)->reader(); + + reader.getRecordHeader(); + const ESM4::RecordHeader& hdr = reader.hdr(); + + if (hdr.record.typeId != ESM4::REC_GRUP) + return loadTes4Record(esm); + + switch (hdr.group.type) + { + case ESM4::Grp_RecordType: + { + // FIXME: rewrite to workaround reliability issue + if (hdr.group.label.value == ESM4::REC_NAVI || hdr.group.label.value == ESM4::REC_WRLD || + hdr.group.label.value == ESM4::REC_REGN || hdr.group.label.value == ESM4::REC_STAT || + hdr.group.label.value == ESM4::REC_ANIO || hdr.group.label.value == ESM4::REC_CONT || + hdr.group.label.value == ESM4::REC_MISC || hdr.group.label.value == ESM4::REC_ACTI || + hdr.group.label.value == ESM4::REC_ARMO || hdr.group.label.value == ESM4::REC_NPC_ || + hdr.group.label.value == ESM4::REC_FLOR || hdr.group.label.value == ESM4::REC_GRAS || + hdr.group.label.value == ESM4::REC_TREE || hdr.group.label.value == ESM4::REC_LIGH || + hdr.group.label.value == ESM4::REC_BOOK || hdr.group.label.value == ESM4::REC_FURN || + hdr.group.label.value == ESM4::REC_SOUN || hdr.group.label.value == ESM4::REC_WEAP || + hdr.group.label.value == ESM4::REC_DOOR || hdr.group.label.value == ESM4::REC_AMMO || + hdr.group.label.value == ESM4::REC_CLOT || hdr.group.label.value == ESM4::REC_ALCH || + hdr.group.label.value == ESM4::REC_APPA || hdr.group.label.value == ESM4::REC_INGR || + hdr.group.label.value == ESM4::REC_SGST || hdr.group.label.value == ESM4::REC_SLGM || + hdr.group.label.value == ESM4::REC_KEYM || hdr.group.label.value == ESM4::REC_HAIR || + hdr.group.label.value == ESM4::REC_EYES || hdr.group.label.value == ESM4::REC_CELL || + hdr.group.label.value == ESM4::REC_CREA || hdr.group.label.value == ESM4::REC_LVLC || + hdr.group.label.value == ESM4::REC_LVLI || hdr.group.label.value == ESM4::REC_MATO || + hdr.group.label.value == ESM4::REC_IDLE || hdr.group.label.value == ESM4::REC_LTEX || + hdr.group.label.value == ESM4::REC_RACE || hdr.group.label.value == ESM4::REC_SBSP + ) + { + reader.saveGroupStatus(); + loadTes4Group(esm); + } + else + { + // Skip groups that are of no interest (for now). + // GMST GLOB CLAS FACT SKIL MGEF SCPT ENCH SPEL BSGN WTHR CLMT DIAL + // QUST PACK CSTY LSCR LVSP WATR EFSH + + // FIXME: The label field of a group is not reliable, so we will need to check here as well + //std::cout << "skipping group... " << ESM4::printLabel(hdr.group.label, hdr.group.type) << std::endl; + reader.skipGroup(); + return; + } + + break; + } + case ESM4::Grp_CellChild: + case ESM4::Grp_WorldChild: + case ESM4::Grp_TopicChild: + case ESM4::Grp_CellPersistentChild: + { + reader.adjustGRUPFormId(); // not needed or even shouldn't be done? (only labels anyway) + reader.saveGroupStatus(); +//#if 0 + // Below test shows that Oblivion.esm does not have any persistent cell child + // groups under exterior world sub-block group. Haven't checked other files yet. + if (reader.grp(0).type == ESM4::Grp_CellPersistentChild && + reader.grp(1).type == ESM4::Grp_CellChild && + !(reader.grp(2).type == ESM4::Grp_WorldChild || reader.grp(2).type == ESM4::Grp_InteriorSubCell)) + std::cout << "Unexpected persistent child group in exterior subcell" << std::endl; +//#endif + if (!esm.hasMoreRecs()) + return; // may have been an empty group followed by EOF + + loadTes4Group(esm); + + break; + } + case ESM4::Grp_CellTemporaryChild: + case ESM4::Grp_CellVisibleDistChild: + { + // NOTE: preload strategy and persistent records + // + // Current strategy defers loading of "temporary" or "visible when distant" + // references and other records (land and pathgrid) until they are needed. + // + // The "persistent" records need to be loaded up front, however. This is to allow, + // for example, doors to work. A door reference will have a FormId of the + // destination door FormId. But we have no way of knowing to which cell the + // destination FormId belongs until that cell and that reference is loaded. + // + // For worldspaces the persistent records are usully (always?) stored in a dummy + // cell under a "world child" group. It may be possible to skip the whole "cell + // child" group without scanning for persistent records. See above short test. + reader.skipGroup(); + break; + } + case ESM4::Grp_ExteriorCell: + case ESM4::Grp_ExteriorSubCell: + case ESM4::Grp_InteriorCell: + case ESM4::Grp_InteriorSubCell: + { + reader.saveGroupStatus(); + loadTes4Group(esm); + + break; + } + default: + reader.skipGroup(); + break; + } + + return; +} + +void ESMStore::loadTes4Record (ESM::ESMReader& esm) +{ + // Assumes that the reader has just read the record header only. + ESM4::Reader& reader = static_cast(&esm)->reader(); + const ESM4::RecordHeader& hdr = reader.hdr(); + + switch (hdr.record.typeId) + { + + // FIXME: removed for now + + default: + reader.skipRecordData(); + } + + return; +} + void ESMStore::setUp() { std::map::iterator it = mStores.begin(); @@ -213,7 +357,7 @@ void ESMStore::setUp() if (type==ESM::REC_NPC_) { // NPC record will always be last and we know that there can be only one - // dynamic NPC record (player) -> We are done here with dynamic record laoding + // dynamic NPC record (player) -> We are done here with dynamic record loading setUp(); const ESM::NPC *player = mNpcs.find ("player"); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 05b633956..a09929840 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -6,6 +6,12 @@ #include #include "store.hpp" +namespace ESM4 +{ + class Reader; + union RecordHeader; +} + namespace Loading { class Listener; @@ -73,6 +79,9 @@ namespace MWWorld unsigned int mDynamicCount; + void loadTes4Group (ESM::ESMReader& esm); + void loadTes4Record (ESM::ESMReader& esm); + public: /// \todo replace with SharedIterator typedef std::map::const_iterator iterator; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 9b6f9fef9..1303b31da 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -6,16 +6,16 @@ set (VERSION_HPP ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp) if (GIT_CHECKOUT) add_custom_target (git-version COMMAND ${CMAKE_COMMAND} - -DGIT_EXECUTABLE=${GIT_EXECUTABLE} - -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} - -DVERSION_HPP_IN=${VERSION_HPP_IN} - -DVERSION_HPP=${VERSION_HPP} - -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} - -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} - -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} - -DOPENMW_VERSION=${OPENMW_VERSION} - -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake - VERBATIM) + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} + -DVERSION_HPP_IN=${VERSION_HPP_IN} + -DVERSION_HPP=${VERSION_HPP} + -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} + -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} + -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} + -DOPENMW_VERSION=${OPENMW_VERSION} + -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake + VERBATIM) else (GIT_CHECKOUT) configure_file(${VERSION_HPP_IN} ${VERSION_HPP}) endif (GIT_CHECKOUT) @@ -62,7 +62,7 @@ add_component_dir (esm loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile - aisequence magiceffects util custommarkerstate stolenitems transport + aisequence magiceffects util custommarkerstate stolenitems transport esm4reader ) add_component_dir (esmterrain @@ -165,10 +165,10 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) -target_link_libraries(components - ${Boost_LIBRARIES} +target_link_libraries(components + ${Boost_LIBRARIES} ${OGRE_LIBRARIES} - ${OENGINE_LIBRARY} + ${OENGINE_LIBRARY} ) if (WIN32) diff --git a/components/esm/esm4reader.cpp b/components/esm/esm4reader.cpp new file mode 100644 index 000000000..4a2f5c2f8 --- /dev/null +++ b/components/esm/esm4reader.cpp @@ -0,0 +1,94 @@ +#include "esm4reader.hpp" + +ESM::ESM4Reader::ESM4Reader(bool oldHeader) +{ + // TES4 header size is 4 bytes smaller than TES5 header + mReader.setRecHeaderSize(oldHeader ? sizeof(ESM4::RecordHeader)-4 : sizeof(ESM4::RecordHeader)); +} + +ESM::ESM4Reader::~ESM4Reader() +{ +} + +void ESM::ESM4Reader::openTes4File(const std::string &name) +{ + mCtx.filename = name; + // WARNING: may throw + mCtx.leftFile = mReader.openTes4File(name); + mReader.registerForUpdates(this); // for updating mCtx.leftFile + + mReader.getRecordHeader(); + if (mReader.hdr().record.typeId == ESM4::REC_TES4) + { + mReader.loadHeader(); + mCtx.leftFile -= mReader.hdr().record.dataSize; + + // Hack: copy over values to TES3 header for getVer() and getRecordCount() to work + mHeader.mData.version = mReader.esmVersion(); + mHeader.mData.records = mReader.numRecords(); + + mReader.buildLStringIndex(); // for localised strings in Skyrim + } + else + fail("Unknown file format"); +} + +ESM4::ReaderContext ESM::ESM4Reader::getESM4Context() +{ + return mReader.getContext(); +} + +void ESM::ESM4Reader::restoreESM4Context(const ESM4::ReaderContext& ctx) +{ + // Reopen the file if necessary + if (mCtx.filename != ctx.filename) + openTes4File(ctx.filename); + + // mCtx.leftFile is the only thing used in the old context. Strictly speaking, updating it + // with the correct value is not really necessary since we're not going to load the rest of + // the file (most likely to load a CELL or LAND then be done with it). + mCtx.leftFile = mReader.getFileSize() - mReader.getFileOffset(); + + // restore group stack, load the header, etc. + mReader.restoreContext(ctx); +} + +void ESM::ESM4Reader::restoreCellChildrenContext(const ESM4::ReaderContext& ctx) +{ + // Reopen the file if necessary + if (mCtx.filename != ctx.filename) + openTes4File(ctx.filename); + + mReader.restoreContext(ctx); // restore group stack, load the CELL header, etc. + if (mReader.hdr().record.typeId != ESM4::REC_CELL) // FIXME: testing only + fail("Restore Cell Children failed"); + mReader.skipRecordData(); // skip the CELL record + + mReader.getRecordHeader(); // load the header for cell child group (hopefully) + // this is a hack to load only the cell child group... + if (mReader.hdr().group.typeId == ESM4::REC_GRUP && mReader.hdr().group.type == ESM4::Grp_CellChild) + { + mCtx.leftFile = mReader.hdr().group.groupSize - ctx.recHeaderSize; + return; + } + + // But some cells may have no child groups... + // Suspect "ICMarketDistrict" 7 18 is one, followed by cell record 00165F2C "ICMarketDistrict" 6 17 + if (mReader.hdr().group.typeId != ESM4::REC_GRUP && mReader.hdr().record.typeId == ESM4::REC_CELL) + { + mCtx.leftFile = 0; + return; + } + + // Maybe the group is completed + // See "ICMarketDistrict" 9 15 which is followed by a exterior sub-cell block + ESM4::ReaderContext tempCtx = mReader.getContext(); + if (!tempCtx.groupStack.empty() && tempCtx.groupStack.back().second == 0) + { + mCtx.leftFile = 0; + return; + } + else + fail("Restore Cell Children failed"); + +} diff --git a/components/esm/esm4reader.hpp b/components/esm/esm4reader.hpp new file mode 100644 index 000000000..c17647386 --- /dev/null +++ b/components/esm/esm4reader.hpp @@ -0,0 +1,38 @@ +#ifndef COMPONENT_ESM_4READER_H +#define COMPONENT_ESM_4READER_H + +#include +#include + +#include "esmreader.hpp" + +namespace ESM +{ + // Wrapper class for integrating into OpenCS + class ESM4Reader : public ESMReader, public ESM4::ReaderObserver + { + ESM4::Reader mReader; + + public: + + ESM4Reader(bool oldHeader = true); + virtual ~ESM4Reader(); + + ESM4::Reader& reader() { return mReader; } + + // Added for use with OpenMW (loading progress bar) + inline size_t getFileSize() { return mReader.getFileSize(); } + inline size_t getFileOffset() { return mReader.getFileOffset(); } + + // Added for loading Cell/Land + ESM4::ReaderContext getESM4Context(); + void restoreESM4Context(const ESM4::ReaderContext& ctx); + void restoreCellChildrenContext(const ESM4::ReaderContext& ctx); + + void openTes4File(const std::string &name); + + // callback from mReader to ensure hasMoreRecs() can reliably track to EOF + inline void update(std::size_t size) { mCtx.leftFile -= size; } + }; +} +#endif // COMPONENT_ESM_4READER_H From 1b0bac040ab03497c9a94144a1bac649140b5a7c Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 7 Oct 2018 09:45:21 +1100 Subject: [PATCH 364/441] OpenCS Integration. Read the ESM/ESP records but do nothing with them for the moment. --- apps/opencs/CMakeLists.txt | 1 + apps/opencs/model/world/data.cpp | 157 ++++++++++++++++++++++++++++++- apps/opencs/model/world/data.hpp | 8 ++ 3 files changed, 165 insertions(+), 1 deletion(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index ec686c4e2..6543f40d2 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -211,6 +211,7 @@ target_link_libraries(openmw-cs ${OGRE_Overlay_LIBRARIES} ${OGRE_STATIC_PLUGINS} ${SHINY_LIBRARIES} + ${ESM4_LIBRARIES} ${ZLIB_LIBRARY} ${MURMURHASH_LIBRARIES} ${BSAOPTHASH_LIBRARIES} diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index d723ddaac..1f9ebfe73 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -8,10 +8,13 @@ #include #include +#include #include #include #include +#include + #include "idtable.hpp" #include "idtree.hpp" #include "columnimp.hpp" @@ -933,9 +936,24 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mReader = new ESM::ESMReader; mReader->setEncoder (&mEncoder); - mReader->setIndex(mReaderIndex++); + mReader->setIndex(mReaderIndex++); // NOTE: auto increment mReader->open (path.string()); + int esmVer = mReader->getVer(); + bool isTes4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + bool isTes5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_17; + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + if (isTes4 || isTes5 || isFONV) + { + mReader->close(); + delete mReader; + mReader = new ESM::ESM4Reader(isTes4); // TES4 headers are 4 bytes shorter + mReader->setEncoder(&mEncoder); + mReader->setIndex(mReaderIndex-1); // use the same index + static_cast(mReader)->reader().setModIndex(mReaderIndex-1); + static_cast(mReader)->openTes4File(path.string()); + static_cast(mReader)->reader().updateModIndicies(mLoadedFiles); + } mLoadedFiles.push_back(path.filename().string()); // at this point mReader->mHeader.mMaster have been populated for the file being loaded @@ -986,6 +1004,16 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) if (!mReader) throw std::logic_error ("can't continue loading, because no load has been started"); + int esmVer = mReader->getVer(); + bool isTes4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + bool isTes5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_17; + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + // Check if previous record/group was the final one in this group. Must be done before + // calling mReader->hasMoreRecs() below, because all records may have been processed when + // the previous group is popped off the stack. + if (isTes4 || isTes5 || isFONV) + static_cast(mReader)->reader().checkGroupStatus(); + if (!mReader->hasMoreRecs()) { if (mBase) @@ -1005,6 +1033,9 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) return true; } + if (isTes4 || isTes5 || isFONV) + return loadTes4Group(messages); + ESM::NAME n = mReader->getRecName(); mReader->getRecHeader(); @@ -1278,3 +1309,127 @@ const CSMWorld::Data& CSMWorld::Data::self () { return *this; } +bool CSMWorld::Data::loadTes4Group (CSMDoc::Messages& messages) +{ + ESM4::Reader& reader = static_cast(mReader)->reader(); + + // check for EOF, sometimes there is a empty group at the end e.g. FONV DeadMoney.esm + if (!reader.getRecordHeader() || !mReader->hasMoreRecs()) + return false; + + const ESM4::RecordHeader& hdr = reader.hdr(); + + if (hdr.record.typeId != ESM4::REC_GRUP) + return loadTes4Record(hdr, messages); + + // Skip groups that are of no interest. See also: + // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format#Hierarchical_Top_Groups + switch (hdr.group.type) + { + case ESM4::Grp_RecordType: + { + // FIXME: rewrite to workaround reliability issue + if (hdr.group.label.value == ESM4::REC_NAVI || hdr.group.label.value == ESM4::REC_WRLD || + hdr.group.label.value == ESM4::REC_REGN || hdr.group.label.value == ESM4::REC_STAT || + hdr.group.label.value == ESM4::REC_ANIO || hdr.group.label.value == ESM4::REC_CONT || + hdr.group.label.value == ESM4::REC_MISC || hdr.group.label.value == ESM4::REC_ACTI || + hdr.group.label.value == ESM4::REC_ARMO || hdr.group.label.value == ESM4::REC_NPC_ || + hdr.group.label.value == ESM4::REC_FLOR || hdr.group.label.value == ESM4::REC_GRAS || + hdr.group.label.value == ESM4::REC_TREE || hdr.group.label.value == ESM4::REC_LIGH || + hdr.group.label.value == ESM4::REC_BOOK || hdr.group.label.value == ESM4::REC_FURN || + hdr.group.label.value == ESM4::REC_SOUN || hdr.group.label.value == ESM4::REC_WEAP || + hdr.group.label.value == ESM4::REC_DOOR || hdr.group.label.value == ESM4::REC_AMMO || + hdr.group.label.value == ESM4::REC_CLOT || hdr.group.label.value == ESM4::REC_ALCH || + hdr.group.label.value == ESM4::REC_APPA || hdr.group.label.value == ESM4::REC_INGR || + hdr.group.label.value == ESM4::REC_SGST || hdr.group.label.value == ESM4::REC_SLGM || + hdr.group.label.value == ESM4::REC_KEYM || hdr.group.label.value == ESM4::REC_HAIR || + hdr.group.label.value == ESM4::REC_EYES || hdr.group.label.value == ESM4::REC_CELL || + hdr.group.label.value == ESM4::REC_CREA || hdr.group.label.value == ESM4::REC_LVLC || + hdr.group.label.value == ESM4::REC_LVLI || hdr.group.label.value == ESM4::REC_MATO || + hdr.group.label.value == ESM4::REC_IDLE || hdr.group.label.value == ESM4::REC_LTEX + ) + { + // NOTE: The label field of a group is not reliable. See: + // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format + // + // ASCII Q 0x51 0101 0001 + // A 0x41 0100 0001 + // + // Ignore flag 0000 1000 (i.e. probably unrelated) + // + // Workaround by getting the record header and checking its typeId + reader.saveGroupStatus(); + // FIXME: comment may no longer be releavant + loadTes4Group(messages); // CELL group with record type may have sub-groups + } + else + { + //std::cout << "Skipping group... " // FIXME: testing only + //<< ESM4::printLabel(hdr.group.label, hdr.group.type) << std::endl; + + reader.skipGroup(); + return false; + } + + break; + } + case ESM4::Grp_CellChild: + { + reader.adjustGRUPFormId(); // not needed or even shouldn't be done? (only labels anyway) + reader.saveGroupStatus(); + if (!mReader->hasMoreRecs()) + return false; // may have been an empty group followed by EOF + + loadTes4Group(messages); + + break; + } + case ESM4::Grp_WorldChild: + case ESM4::Grp_TopicChild: + // FIXME: need to save context if skipping + case ESM4::Grp_CellPersistentChild: + case ESM4::Grp_CellTemporaryChild: + case ESM4::Grp_CellVisibleDistChild: + { + reader.adjustGRUPFormId(); // not needed or even shouldn't be done? (only labels anyway) + reader.saveGroupStatus(); + if (!mReader->hasMoreRecs()) + return false; // may have been an empty group followed by EOF + + loadTes4Group(messages); + + break; + } + case ESM4::Grp_ExteriorCell: + case ESM4::Grp_ExteriorSubCell: + case ESM4::Grp_InteriorCell: + case ESM4::Grp_InteriorSubCell: + { + reader.saveGroupStatus(); + loadTes4Group(messages); + + break; + } + default: + break; + } + + return false; +} + +// Deal with Tes4 records separately, as some have the same name as Tes3, e.g. REC_CELL +bool CSMWorld::Data::loadTes4Record (const ESM4::RecordHeader& hdr, CSMDoc::Messages& messages) +{ + ESM4::Reader& reader = static_cast(mReader)->reader(); + + switch (hdr.record.typeId) + { + + // FIXME: removed for now + + default: + reader.skipRecordData(); + } + + return false; +} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 47046d9de..998e2866a 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -57,6 +57,11 @@ namespace ESM struct Dialogue; } +namespace ESM4 +{ + union RecordHeader; +} + namespace CSMWorld { class ResourcesManager; @@ -127,6 +132,9 @@ namespace CSMWorld const Data& self (); + bool loadTes4Group (CSMDoc::Messages& messages); + bool loadTes4Record (const ESM4::RecordHeader& hdr, CSMDoc::Messages& messages); + public: Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager); From c68ea9159ecb4b5f23728c8de2df5261a426f003 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 7 Oct 2018 09:52:22 +1100 Subject: [PATCH 365/441] README update. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 579f8c161..e8f6d7ff7 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,9 @@ Enhancements for both OpenMW and OpenCS: * Hash based lookup for TES3 BSA files. * TES4/TES5 BSA support. -* Experimental support of loading TES4/TES5 records (coming soon). +* Loading TES4/TES5 records (incomplete). +* Experimental support of using multiple versions of ESM files concurrently in OpenMW (coming soon) +* Experimental support of loading newer NIF records (coming soon). * Experimental support of NavMesh (eventually). openmw.cfg example From e073e0d8c96777b22157c7790341c9e4f80d459e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 8 Dec 2018 20:37:38 +0400 Subject: [PATCH 366/441] Do not read font size just for every single widget --- components/widgets/fontwrapper.hpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/components/widgets/fontwrapper.hpp b/components/widgets/fontwrapper.hpp index 2424b22a7..84406c70c 100644 --- a/components/widgets/fontwrapper.hpp +++ b/components/widgets/fontwrapper.hpp @@ -14,18 +14,10 @@ namespace Gui virtual void setFontName(const std::string& name) { T::setFontName(name); - T::setPropertyOverride ("FontHeight", mFontSize); + T::setPropertyOverride ("FontHeight", getFontSize()); } protected: - FontWrapper() - { - // Note: we can not use the WindowManager here, so there is a code duplication a bit. - int fontSize = Settings::Manager::getInt("font size", "GUI"); - fontSize = std::min(std::max(12, fontSize), 20); - mFontSize = std::to_string(fontSize); - } - virtual void setPropertyOverride(const std::string& _key, const std::string& _value) { T::setPropertyOverride (_key, _value); @@ -34,11 +26,22 @@ namespace Gui // We should restore it. if (_key == "FontName") { - T::setPropertyOverride ("FontHeight", mFontSize); + T::setPropertyOverride ("FontHeight", getFontSize()); } } - std::string mFontSize; + private: + static int clamp(const int& value, const int& lowBound, const int& highBound) + { + return std::min(std::max(lowBound, value), highBound); + } + + std::string getFontSize() + { + // Note: we can not use the WindowManager here, so there is a code duplication a bit. + static const std::string fontSize = std::to_string(clamp(Settings::Manager::getInt("font size", "GUI"), 12, 20)); + return fontSize; + } }; } From 53188f61fcec55150046dad4bd4a1cd5ec68a3c6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 18 Dec 2018 19:44:30 +0000 Subject: [PATCH 367/441] Fix animation bug with VAOs --- components/sceneutil/morphgeometry.cpp | 6 ++++++ components/sceneutil/riggeometry.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp index 1b7e4ca93..01bb35c45 100644 --- a/components/sceneutil/morphgeometry.cpp +++ b/components/sceneutil/morphgeometry.cpp @@ -2,6 +2,8 @@ #include +#include + namespace SceneUtil { @@ -176,6 +178,10 @@ void MorphGeometry::cull(osg::NodeVisitor *nv) positionDst->dirty(); +#if OSG_MIN_VERSION_REQUIRED(3, 5, 6) + geom.dirtyGLObjects(); +#endif + nv->pushOntoNodePath(&geom); nv->apply(geom); nv->popFromNodePath(); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 30a3f076c..b086cf628 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include "skeleton.hpp" @@ -235,6 +237,10 @@ void RigGeometry::cull(osg::NodeVisitor* nv) if (tangentDst) tangentDst->dirty(); +#if OSG_MIN_VERSION_REQUIRED(3, 5, 6) + geom.dirtyGLObjects(); +#endif + nv->pushOntoNodePath(&geom); nv->apply(geom); nv->popFromNodePath(); From 7c3ef213ced8b6676180742c5179807291f50e6b Mon Sep 17 00:00:00 2001 From: Alexander Kolodeev Date: Wed, 19 Dec 2018 18:47:02 +0300 Subject: [PATCH 368/441] Fix error when create new OpenCS addon --- components/contentselector/model/contentmodel.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 39e91d5a3..7a4558d57 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -474,9 +474,13 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) void ContentSelectorModel::ContentModel::clearFiles() { - beginRemoveRows(QModelIndex(), 0, mFiles.count()-1); - mFiles.clear(); - endRemoveRows(); + const int filesCount = mFiles.count(); + + if (filesCount > 0) { + beginRemoveRows(QModelIndex(), 0, filesCount - 1); + mFiles.clear(); + endRemoveRows(); + } } QStringList ContentSelectorModel::ContentModel::gameFiles() const From 2681435857a65387df3ef250dd3df6fe687bea22 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 20 Dec 2018 19:32:16 +0300 Subject: [PATCH 369/441] Editor: handle AI settings, regional sound and levelled list "none" chances as shorts (bug #2987) --- CHANGELOG.md | 1 + .../opencs/model/tools/referenceablecheck.cpp | 19 +++++++++++++++++++ apps/opencs/model/tools/regioncheck.cpp | 6 ++++++ apps/opencs/model/world/data.cpp | 2 +- apps/opencs/model/world/refidcollection.cpp | 10 +++++----- 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d28da21ce..1256da340 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.46.0 ------ + Bug #2987: Editor: some chance and AI data fields can overflow Bug #3623: Fix HiDPI on Windows Bug #4540: Rain delay when exiting water Bug #4701: PrisonMarker record is not hardcoded like other markers diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index fdbab7fd0..73c28f564 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -470,6 +470,13 @@ void CSMTools::ReferenceableCheckStage::creatureCheck ( if (creature.mData.mSoul < 0) messages.add(id, "Soul value is negative", "", CSMDoc::Message::Severity_Error); + if (creature.mAiData.mAlarm > 100) + messages.add(id, "Alarm rating is over 100", "", CSMDoc::Message::Severity_Warning); + if (creature.mAiData.mFight > 100) + messages.add(id, "Fight rating is over 100", "", CSMDoc::Message::Severity_Warning); + if (creature.mAiData.mFlee > 100) + messages.add(id, "Flee rating is over 100", "", CSMDoc::Message::Severity_Warning); + for (int i = 0; i < 6; ++i) { if (creature.mData.mAttack[i] < 0) @@ -700,6 +707,13 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( if (level <= 0) messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); + if (npc.mAiData.mAlarm > 100) + messages.add(id, "Alarm rating is over 100", "", CSMDoc::Message::Severity_Warning); + if (npc.mAiData.mFight > 100) + messages.add(id, "Fight rating is over 100", "", CSMDoc::Message::Severity_Warning); + if (npc.mAiData.mFlee > 100) + messages.add(id, "Flee rating is over 100", "", CSMDoc::Message::Severity_Warning); + if (gold < 0) messages.add(id, "Gold count is negative", "", CSMDoc::Message::Severity_Error); @@ -1014,6 +1028,11 @@ template void CSMTools::ReferenceableCheckStage::toolCheck ( template void CSMTools::ReferenceableCheckStage::listCheck ( const List& someList, CSMDoc::Messages& messages, const std::string& someID) { + if (someList.mChanceNone > 100) + { + messages.add(someID, "Chance that no object is used is over 100 percent", "", CSMDoc::Message::Severity_Warning); + } + for (unsigned i = 0; i < someList.mList.size(); ++i) { if (mReferencables.searchId(someList.mList[i].mId).first == -1) diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 0b6537d7f..1e6f64786 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -42,5 +42,11 @@ void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages) if (chances != 100) messages.add(id, "Weather chances do not add up to 100", "", CSMDoc::Message::Severity_Error); + for (const ESM::Region::SoundRef& sound : region.mSoundList) + { + if (sound.mChance > 100) + messages.add(id, "Chance of '" + sound.mSound.toString() + "' sound to play is over 100 percent", "", CSMDoc::Message::Severity_Warning); + } + /// \todo check data members that can't be edited in the table view } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 53bd04147..3f18e8bd6 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -205,7 +205,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_SoundName, ColumnBase::Display_Sound)); mRegions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SoundChance, ColumnBase::Display_Integer)); + new NestedChildColumn (Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8)); mBirthsigns.addColumn (new StringIdColumn); mBirthsigns.addColumn (new RecordStateColumn); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 272722f64..89d346204 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -128,13 +128,13 @@ CSMWorld::RefIdCollection::RefIdCollection() ActorColumns actorsColumns (nameColumns); - mColumns.push_back (RefIdColumn (Columns::ColumnId_AiHello, ColumnBase::Display_Integer)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_AiHello, ColumnBase::Display_UnsignedInteger8)); actorsColumns.mHello = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFlee, ColumnBase::Display_Integer)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFlee, ColumnBase::Display_UnsignedInteger8)); actorsColumns.mFlee = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFight, ColumnBase::Display_Integer)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFight, ColumnBase::Display_UnsignedInteger8)); actorsColumns.mFight = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_AiAlarm, ColumnBase::Display_Integer)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_AiAlarm, ColumnBase::Display_UnsignedInteger8)); actorsColumns.mAlarm = &mColumns.back(); // Nested table @@ -645,7 +645,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_LevelledItemType, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_LevelledItemChanceNone, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_LevelledItemChanceNone, CSMWorld::ColumnBase::Display_UnsignedInteger8)); mAdapters.insert (std::make_pair (UniversalId::Type_Activator, new NameRefIdAdapter (UniversalId::Type_Activator, nameColumns))); From f98a454ec15c62d98340c735fe6dcf71729d796e Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 16 Dec 2018 09:25:09 +0100 Subject: [PATCH 370/441] Add a script to verify macOS package contents --- .travis.yml | 1 + CI/check_package.osx.sh | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100755 CI/check_package.osx.sh diff --git a/.travis.yml b/.travis.yml index ef0a945a5..3d426d753 100644 --- a/.travis.yml +++ b/.travis.yml @@ -90,6 +90,7 @@ script: - cd ./build - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi + - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi - cd "${TRAVIS_BUILD_DIR}" diff --git a/CI/check_package.osx.sh b/CI/check_package.osx.sh new file mode 100755 index 000000000..f5da5c273 --- /dev/null +++ b/CI/check_package.osx.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +hdiutil attach ./*.dmg -mountpoint "${TRAVIS_BUILD_DIR}/openmw-package" > /dev/null || echo "hdutil has failed" + +EXPECTED_PACKAGE_FILES=('Applications' 'OpenMW-CS.app' 'OpenMW.app') +PACKAGE_FILES=$(ls "${TRAVIS_BUILD_DIR}/openmw-package" | LC_ALL=C sort) + +DIFF=$(diff <(printf "%s\n" "${EXPECTED_PACKAGE_FILES[@]}") <(printf "%s\n" "${PACKAGE_FILES[@]}")) +DIFF_STATUS=$? + +if [[ $DIFF_STATUS -ne 0 ]]; then + echo "The package should only contain an Applications symlink and two applications, see the following diff for details." >&2 + echo "$DIFF" >&2 + exit 1 +fi From c3a2a2d73f3bc7bad90a271d9dfcfc3e8d6364aa Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Thu, 20 Dec 2018 19:53:51 +0100 Subject: [PATCH 371/441] Do not package Recastnavigation headers and static libs --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22b28a062..48df24774 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -604,7 +604,7 @@ set(RECASTNAVIGATION_DEMO OFF CACHE BOOL "Do not build RecastDemo") set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries") set(RECASTNAVIGATION_TESTS OFF CACHE BOOL "Do not build recastnavigation tests") -add_subdirectory (extern/recastnavigation) +add_subdirectory (extern/recastnavigation EXCLUDE_FROM_ALL) add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) if (BUILD_OPENCS) From e0161bb2bf1ac8b3f0e0d63179c0be0bb0967c52 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Thu, 20 Dec 2018 21:20:56 +0100 Subject: [PATCH 372/441] Update macOS dependencies, use Xcode 10.1 --- .travis.yml | 4 ++-- CI/before_install.osx.sh | 2 +- CI/before_script.osx.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index ef0a945a5..843fcb655 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,9 +37,9 @@ addons: build_command: "make VERBOSE=1 -j3" matrix: include: - - name: OpenMW (all) on MacOS xcode9.4 + - name: OpenMW (all) on macOS Xcode 10.1 os: osx - osx_image: xcode9.4 + osx_image: xcode10.1 if: branch != coverity_scan - name: OpenMW (all) on Ubuntu Xenial GCC-5 os: linux diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index d75ee3be5..722c1ad8c 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -6,5 +6,5 @@ brew outdated cmake || brew upgrade cmake brew outdated pkgconfig || brew upgrade pkgconfig brew install qt -curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-7cf2789.zip -o ~/openmw-deps.zip +curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-55c27b1.zip -o ~/openmw-deps.zip unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 16f461639..d3994a7ae 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -11,7 +11,7 @@ cd build cmake \ -D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \ -D CMAKE_OSX_DEPLOYMENT_TARGET="10.9" \ --D CMAKE_OSX_SYSROOT="macosx10.13" \ +-D CMAKE_OSX_SYSROOT="macosx10.14" \ -D CMAKE_BUILD_TYPE=Release \ -D OPENMW_OSX_DEPLOYMENT=TRUE \ -D DESIRED_QT_VERSION=5 \ From 09585b7208d6317c52980be47bc73c2e9e2a2176 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Sat, 22 Dec 2018 22:21:26 -0500 Subject: [PATCH 373/441] android_main.c: Replace nullptr back to NULL since there are no nullptr in C --- apps/openmw/android_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c index 9a7b6bc50..d234a369d 100644 --- a/apps/openmw/android_main.c +++ b/apps/openmw/android_main.c @@ -20,14 +20,14 @@ void releaseArgv(); int Java_org_libsdl_app_SDLActivity_getMouseX(JNIEnv *env, jclass cls, jobject obj) { int ret = 0; - SDL_GetMouseState(&ret, nullptr); + SDL_GetMouseState(&ret, NULL); return ret; } int Java_org_libsdl_app_SDLActivity_getMouseY(JNIEnv *env, jclass cls, jobject obj) { int ret = 0; - SDL_GetMouseState(nullptr, &ret); + SDL_GetMouseState(NULL, &ret); return ret; } From 0bbcc0e787a12fd5ec3dfc940b503ca814eab57c Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 23 Dec 2018 13:39:35 +0300 Subject: [PATCH 374/441] Avoid falling height reset if onGround state didn't change (bug #4411) --- CHANGELOG.md | 1 + apps/openmw/mwphysics/physicssystem.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1256da340..190ba3692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Bug #2987: Editor: some chance and AI data fields can overflow Bug #3623: Fix HiDPI on Windows + Bug #4411: Reloading a saved game while falling prevents damage in some cases Bug #4540: Rain delay when exiting water Bug #4701: PrisonMarker record is not hardcoded like other markers Bug #4714: Crash upon game load in the repair menu while the "Your repair failed!" message is active diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 836370255..a4fd2c1ca 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1339,7 +1339,7 @@ namespace MWPhysics float heightDiff = position.z() - oldHeight; MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); - if ((wasOnGround && physicActor->getOnGround()) || flying || world->isSwimming(iter->first) || slowFall < 1) + if ((numSteps > 0 && wasOnGround && physicActor->getOnGround()) || flying || world->isSwimming(iter->first) || slowFall < 1) stats.land(iter->first == player); else if (heightDiff < 0) stats.addToFallHeight(-heightDiff); From 793c2a19d28f57032f33f12690aad831cd11f979 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 23 Dec 2018 19:05:00 +0300 Subject: [PATCH 375/441] Cherry-pick cc9cii's editor improvements --- apps/opencs/model/world/idtable.cpp | 6 ++- apps/opencs/model/world/refcollection.cpp | 52 +++++++++-------------- apps/opencs/model/world/resourcetable.cpp | 6 ++- 3 files changed, 29 insertions(+), 35 deletions(-) diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index cb48fc85f..a0dbd7bad 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -221,7 +221,11 @@ std::string CSMWorld::IdTable::getId(int row) const ///This method can return only indexes to the top level table cells QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const { - return index(mIdCollection->getIndex (id), column); + int row = mIdCollection->searchId (id); + if (row != -1) + return index(row, column); + + return QModelIndex(); } void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& record, CSMWorld::UniversalId::Type type) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 7767cca6d..30ffb310f 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,8 +1,5 @@ #include "refcollection.hpp" -#include - -#include #include #include @@ -32,40 +29,31 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool if (cell.get().isExterior()) { - // ignoring moved references sub-record; instead calculate cell from coordinates + // Autocalculate the cell index from coordinates first std::pair index = ref.getCellIndex(); - std::ostringstream stream; - stream << "#" << index.first << " " << index.second; - - ref.mCell = stream.str(); + ref.mCell = "#" + std::to_string(index.first) + " " + std::to_string(index.second); - if (!base && // don't try to update base records - mref.mRefNum.mIndex != 0) // MVRF tag found + // Handle non-base moved references + if (!base && mref.mRefNum.mIndex != 0) { - // there is a requirement for a placeholder where the original object was - // - // see the forum discussions here for more details: - // https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30 + // Moved references must have a link back to their original cell + // See discussion: https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30 ref.mOriginalCell = cell2.mId; - // It is not always possibe to ignore moved references sub-record and - // calculate from coordinates. Some mods may place the ref in positions - // outside normal bounds, resulting in non sensical cell id's. This often - // happens if the moved ref was deleted. - // - // Use the target cell from the MVRF tag but if different output an error - // message + // Some mods may move references outside of the bounds, which often happens they are deleted. + // This results in nonsensical autocalculated cell IDs, so we must use the record target cell. + + // Log a warning if the record target cell is different if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1]) { - Log(Debug::Warning) << "Warning: the Position of moved ref " - << ref.mRefID << " does not match the target cell"; - Log(Debug::Warning) << "Position: #" << index.first << " " << index.second - <<", Target #"<< mref.mTarget[0] << " " << mref.mTarget[1]; - - stream.clear(); - stream << "#" << mref.mTarget[0] << " " << mref.mTarget[1]; - ref.mCell = stream.str(); // overwrite + std::string indexCell = ref.mCell; + ref.mCell = "#" + std::to_string(mref.mTarget[0]) + " " + std::to_string(mref.mTarget[1]); + + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex)); + messages.add(id, "The position of the moved reference " + ref.mRefID + " (cell " + indexCell + ")" + " does not match the target cell (" + ref.mCell + ")", + std::string(), CSMDoc::Message::Severity_Warning); } } } @@ -87,7 +75,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex)); - messages.add (id, "Attempt to delete a non-existing reference"); + messages.add (id, "Attempt to delete a non-existent reference"); continue; } @@ -140,7 +128,5 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool std::string CSMWorld::RefCollection::getNewId() { - std::ostringstream stream; - stream << "ref#" << mNextId++; - return stream.str(); + return "ref#" + std::to_string(mNextId++); } diff --git a/apps/opencs/model/world/resourcetable.cpp b/apps/opencs/model/world/resourcetable.cpp index f55f87873..0e7864e48 100644 --- a/apps/opencs/model/world/resourcetable.cpp +++ b/apps/opencs/model/world/resourcetable.cpp @@ -110,7 +110,11 @@ QModelIndex CSMWorld::ResourceTable::parent (const QModelIndex& index) const QModelIndex CSMWorld::ResourceTable::getModelIndex (const std::string& id, int column) const { - return index (mResources->getIndex (id), column); + int row = mResources->searchId(id); + if (row != -1) + return index (row, column); + + return QModelIndex(); } int CSMWorld::ResourceTable::searchColumnIndex (Columns::ColumnId id) const From 98b2c044044b60089bb647aa0a67ad9bddabb1b0 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Sun, 1 Apr 2018 18:45:43 -0400 Subject: [PATCH 376/441] android: Add a method so that we can send relative mouse movements from java. --- apps/openmw/android_main.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c index d234a369d..393d48d77 100644 --- a/apps/openmw/android_main.c +++ b/apps/openmw/android_main.c @@ -35,6 +35,17 @@ int Java_org_libsdl_app_SDLActivity_isMouseShown(JNIEnv *env, jclass cls, jobjec return SDL_ShowCursor(SDL_QUERY); } +extern SDL_Window *Android_Window; +int SDL_SendMouseMotion(SDL_Window * window, int mouseID, int relative, int x, int y); +void Java_org_libsdl_app_SDLActivity_sendRelativeMouseMotion(JNIEnv *env, jclass cls, int x, int y) { + SDL_SendMouseMotion(Android_Window, 0, 1, x, y); +} + +int SDL_SendMouseButton(SDL_Window * window, int mouseID, Uint8 state, Uint8 button); +void Java_org_libsdl_app_SDLActivity_sendMouseButton(JNIEnv *env, jclass cls, int state, int button) { + SDL_SendMouseButton(Android_Window, 0, state, button); +} + int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) { setenv("OPENMW_DECOMPRESS_TEXTURES", "1", 1); From 8e7c01b5615f295611530e7cda61c5b10083370a Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Mon, 24 Dec 2018 01:50:58 -0500 Subject: [PATCH 377/441] loadingscreen: Fix UaF in loading screen. When the CopyFramebufferToTextureCallback callback is called, in its operator() it resets setInitialDrawCallback by providing a NULL pointer. However, this causes the callback to get deleted. In turn, the "this" pointer is invalidated. When execution returns to DrawCallback::run, it accesses a _nestedCallback member of deleted "this" which is UB. --- apps/openmw/mwgui/loadingscreen.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 7f641d408..68cf0439a 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -141,10 +141,6 @@ namespace MWGui int w = renderInfo.getCurrentCamera()->getViewport()->width(); int h = renderInfo.getCurrentCamera()->getViewport()->height(); mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h); - - // Callback removes itself when done - if (renderInfo.getCurrentCamera()) - renderInfo.getCurrentCamera()->setInitialDrawCallback(nullptr); } private: @@ -308,6 +304,8 @@ namespace MWGui mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture)); } + // Notice that the next time this is called, the current CopyFramebufferToTextureCallback will be deleted + // so there's no memory leak as at most one object of type CopyFramebufferToTextureCallback is allocated at a time. mViewer->getCamera()->setInitialDrawCallback(new CopyFramebufferToTextureCallback(mTexture)); mBackgroundImage->setBackgroundImage(""); From 07e9ce84b3480a8634c5da8b97fec609326c1ec2 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Mon, 24 Dec 2018 02:49:36 -0500 Subject: [PATCH 378/441] Replace volatile bools with std::atomic --- apps/openmw/mwsound/openal_output.cpp | 5 +++-- apps/openmw/mwworld/cellpreloader.cpp | 6 ++++-- components/sceneutil/workqueue.hpp | 3 ++- extern/osg-ffmpeg-videoplayer/videostate.hpp | 11 ++++++----- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 39e872b0c..ccb3f22b5 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -279,7 +280,7 @@ private: std::unique_ptr mLoudnessAnalyzer; - volatile bool mIsFinished; + std::atomic mIsFinished; void updateAll(bool local); @@ -313,7 +314,7 @@ struct OpenAL_Output::StreamThread : public OpenThreads::Thread { typedef std::vector StreamVec; StreamVec mStreams; - volatile bool mQuitNow; + std::atomic mQuitNow; OpenThreads::Mutex mMutex; OpenThreads::Condition mCondVar; diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index a06c208b5..3bf66a5a1 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -1,5 +1,7 @@ #include "cellpreloader.hpp" +#include + #include #include #include @@ -159,7 +161,7 @@ namespace MWWorld MWRender::LandManager* mLandManager; bool mPreloadInstances; - volatile bool mAbort; + std::atomic mAbort; osg::ref_ptr mTerrainView; @@ -392,7 +394,7 @@ namespace MWWorld } private: - volatile bool mAbort; + std::atomic mAbort; std::vector > mTerrainViews; Terrain::World* mWorld; std::vector mPreloadPositions; diff --git a/components/sceneutil/workqueue.hpp b/components/sceneutil/workqueue.hpp index 2084df328..0b16db0e7 100644 --- a/components/sceneutil/workqueue.hpp +++ b/components/sceneutil/workqueue.hpp @@ -9,6 +9,7 @@ #include #include +#include #include namespace SceneUtil @@ -87,7 +88,7 @@ namespace SceneUtil private: WorkQueue* mWorkQueue; - volatile bool mActive; + std::atomic mActive; }; diff --git a/extern/osg-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp index a60d6032f..6abaa64cd 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.hpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp @@ -2,6 +2,7 @@ #define VIDEOPLAYER_VIDEOSTATE_H #include +#include #include #include #include @@ -78,7 +79,7 @@ struct PacketQueue { { clear(); } AVPacketList *first_pkt, *last_pkt; - volatile bool flushing; + std::atomic flushing; int nb_packets; int size; @@ -169,12 +170,12 @@ struct VideoState { std::unique_ptr parse_thread; std::unique_ptr video_thread; - volatile bool mSeekRequested; + std::atomic mSeekRequested; uint64_t mSeekPos; - volatile bool mVideoEnded; - volatile bool mPaused; - volatile bool mQuit; + std::atomic mVideoEnded; + std::atomic mPaused; + std::atomic mQuit; }; } From 1bdec2399fab05dcd18b8bc8ec3423c9ab26b354 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Tue, 25 Dec 2018 11:56:24 -0500 Subject: [PATCH 379/441] Make sure CopyFramebufferToTextureCallback is only called once and not every frame --- apps/openmw/mwgui/loadingscreen.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 68cf0439a..a9480f261 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -133,11 +133,15 @@ namespace MWGui public: CopyFramebufferToTextureCallback(osg::Texture2D* texture) : mTexture(texture) + , oneshot(true) { } virtual void operator () (osg::RenderInfo& renderInfo) const { + if (!oneshot) + return; + oneshot = false; int w = renderInfo.getCurrentCamera()->getViewport()->width(); int h = renderInfo.getCurrentCamera()->getViewport()->height(); mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h); @@ -145,6 +149,7 @@ namespace MWGui private: osg::ref_ptr mTexture; + mutable bool oneshot; }; class DontComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback From 956934911a3053442ae8876b8ae735eb2b551e07 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Tue, 25 Dec 2018 21:04:21 +0100 Subject: [PATCH 380/441] [macOS, CI] Use CMake 3.12.4 See https://gitlab.com/OpenMW/openmw/issues/4767 for details. --- CI/before_install.osx.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 722c1ad8c..43102356e 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,8 +1,9 @@ #!/bin/sh -e brew update - -brew outdated cmake || brew upgrade cmake +brew unlink cmake || true +brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/a3b64391ebace30b84de8e7997665a1621c0b2c0/Formula/cmake.rb +brew switch cmake 3.12.4 brew outdated pkgconfig || brew upgrade pkgconfig brew install qt From 2306d904c779ff6154cf3d1cfcfb2d8da71702f9 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Wed, 26 Dec 2018 16:57:10 +0100 Subject: [PATCH 381/441] [macOS] Fail CMake when trying to use CMake 3.13 for macOS packaging --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 48df24774..f34e3c83c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -795,6 +795,10 @@ endif() # Apple bundling if (OPENMW_OSX_DEPLOYMENT AND APPLE AND DESIRED_QT_VERSION MATCHES 5) + if (${CMAKE_MAJOR_VERSION} STREQUAL "3" AND ${CMAKE_MINOR_VERSION} STREQUAL "13") + message(FATAL_ERROR "macOS packaging is broken in CMake 3.13.*, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use an older version like 3.12.4") + endif () + get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE) get_filename_component(QT_COCOA_PLUGIN_DIR "${QT_COCOA_PLUGIN_PATH}" DIRECTORY) get_filename_component(QT_COCOA_PLUGIN_GROUP "${QT_COCOA_PLUGIN_DIR}" NAME) From 7155e787b4615c2db0981ab633088b8ad632cbdf Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 27 Dec 2018 23:20:58 +0300 Subject: [PATCH 382/441] Clean up fallback record creation --- apps/opencs/model/world/data.cpp | 14 ++++++++++---- apps/openmw/mwworld/store.cpp | 27 +++++++++++++++------------ components/esm/attr.hpp | 7 ------- components/esm/loaddoor.hpp | 15 --------------- components/esm/loadstat.hpp | 10 ---------- 5 files changed, 25 insertions(+), 48 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 3f18e8bd6..9e9447c5a 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -989,23 +989,29 @@ void CSMWorld::Data::loadFallbackEntries() std::make_pair("PrisonMarker", "marker_prison.nif") }; - for (const std::pair marker : staticMarkers) + for (const std::pair &marker : staticMarkers) { if (mReferenceables.searchId (marker.first)==-1) { + ESM::Static newMarker; + newMarker.mId = marker.first; + newMarker.mModel = marker.second; CSMWorld::Record record; - record.mBase = ESM::Static(marker.first, marker.second); + record.mBase = newMarker; record.mState = CSMWorld::RecordBase::State_BaseOnly; mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Static); } } - for (const std::pair marker : doorMarkers) + for (const std::pair &marker : doorMarkers) { if (mReferenceables.searchId (marker.first)==-1) { + ESM::Door newMarker; + newMarker.mId = marker.first; + newMarker.mModel = marker.second; CSMWorld::Record record; - record.mBase = ESM::Door(marker.first, std::string(), marker.second, std::string(), std::string(), std::string()); + record.mBase = newMarker; record.mState = CSMWorld::RecordBase::State_BaseOnly; mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Door); } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 70f8cf44f..7df61bec3 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1007,14 +1007,13 @@ namespace MWWorld } void Store::setUp() { - for (int i = 0; i < ESM::Attribute::Length; ++i) { - mStatic.push_back( - ESM::Attribute( - ESM::Attribute::sAttributeIds[i], - ESM::Attribute::sGmstAttributeIds[i], - ESM::Attribute::sGmstAttributeDescIds[i] - ) - ); + for (int i = 0; i < ESM::Attribute::Length; ++i) + { + ESM::Attribute newAttribute; + newAttribute.mId = ESM::Attribute::sAttributeIds[i]; + newAttribute.mName = ESM::Attribute::sGmstAttributeIds[i]; + newAttribute.mDescription = ESM::Attribute::sGmstAttributeDescIds[i]; + mStatic.push_back(newAttribute); } } size_t Store::getSize() const @@ -1066,11 +1065,13 @@ namespace MWWorld std::make_pair("travelmarker", "marker_travel.nif") }; - for (const std::pair marker : markers) + for (const std::pair &marker : markers) { if (search(marker.first) == 0) { - ESM::Static newMarker = ESM::Static(marker.first, marker.second); + ESM::Static newMarker; + newMarker.mId = marker.first; + newMarker.mModel = marker.second; std::pair ret = mStatic.insert(std::make_pair(marker.first, newMarker)); if (ret.first != mStatic.end()) { @@ -1088,11 +1089,13 @@ namespace MWWorld std::make_pair("prisonmarker", "marker_prison.nif") }; - for (const std::pair marker : markers) + for (const std::pair &marker : markers) { if (search(marker.first) == 0) { - ESM::Door newMarker = ESM::Door(marker.first, std::string(), marker.second, std::string(), std::string(), std::string()); + ESM::Door newMarker; + newMarker.mId = marker.first; + newMarker.mModel = marker.second; std::pair ret = mStatic.insert(std::make_pair(marker.first, newMarker)); if (ret.first != mStatic.end()) { diff --git a/components/esm/attr.hpp b/components/esm/attr.hpp index 993de6ccb..c0a54c659 100644 --- a/components/esm/attr.hpp +++ b/components/esm/attr.hpp @@ -32,13 +32,6 @@ struct Attribute static const std::string sGmstAttributeIds[Length]; static const std::string sGmstAttributeDescIds[Length]; static const std::string sAttributeIcons[Length]; - - Attribute(AttributeID id, const std::string &name, const std::string &description) - : mId(id) - , mName(name) - , mDescription(description) - { - } }; } #endif diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index 955abec89..3afe5d5e4 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -22,21 +22,6 @@ struct Door void blank(); ///< Set record to default state (does not touch the ID). - - Door(const std::string id, const std::string name, const std::string &model, - const std::string script, const std::string opensound, const std::string closesound) - : mId(id) - , mName(name) - , mModel(model) - , mScript(script) - , mOpenSound(opensound) - , mCloseSound(closesound) - { - } - - Door() - { - } }; } #endif diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index f80875b65..3d9144040 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -33,16 +33,6 @@ struct Static void blank(); ///< Set record to default state (does not touch the ID). - - Static(const std::string id, const std::string &model) - : mId(id) - , mModel(model) - { - } - - Static() - { - } }; } #endif From 467724d5e8b5ede4e8b71b7d8ee4aac49747ba9d Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Fri, 28 Dec 2018 23:49:06 +0300 Subject: [PATCH 383/441] Improve fallback numerical value handling (bug #4768) --- CHANGELOG.md | 1 + components/fallback/fallback.cpp | 84 +++++++++++++++++++------------- 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1256da340..f4fa6ecfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Bug #4745: Editor: Interior cell lighting field values are not displayed as colors Bug #4746: Non-solid player can't run or sneak Bug #4750: Sneaking doesn't work in first person view if the player is in attack ready state + Bug #4768: Fallback numerical value recovery chokes on invalid arguments Feature #2229: Improve pathfinding AI Feature #3442: Default values for fallbacks from ini file Feature #3610: Option to invert X axis diff --git a/components/fallback/fallback.cpp b/components/fallback/fallback.cpp index edc3f2678..70ee2f681 100644 --- a/components/fallback/fallback.cpp +++ b/components/fallback/fallback.cpp @@ -1,70 +1,86 @@ #include "fallback.hpp" -#include - +#include namespace Fallback { - bool stob(std::string const& s) { - return s != "0"; - } - Map::Map(const std::map& fallback):mFallbackMap(fallback) {} std::string Map::getFallbackString(const std::string& fall) const { std::map::const_iterator it; - if((it = mFallbackMap.find(fall)) == mFallbackMap.end()) + if ((it = mFallbackMap.find(fall)) == mFallbackMap.end()) { - return ""; + return std::string(); } return it->second; } float Map::getFallbackFloat(const std::string& fall) const { - std::string fallback=getFallbackString(fall); - if (fallback.empty()) - return 0; - else - return boost::lexical_cast(fallback); + std::string fallback = getFallbackString(fall); + if (!fallback.empty()) + { + try + { + return std::stof(fallback); + } + catch (const std::invalid_argument&) + { + Log(Debug::Error) << "Error: '" << fall << "' setting value (" << fallback << ") is not a valid number, using 0 as a fallback"; + } + } + + return 0; } int Map::getFallbackInt(const std::string& fall) const { - std::string fallback=getFallbackString(fall); - if (fallback.empty()) - return 0; - else - return std::stoi(fallback); + std::string fallback = getFallbackString(fall); + if (!fallback.empty()) + { + try + { + return std::stoi(fallback); + } + catch (const std::invalid_argument&) + { + Log(Debug::Error) << "Error: '" << fall << "' setting value (" << fallback << ") is not a valid number, using 0 as a fallback"; + } + } + + return 0; } bool Map::getFallbackBool(const std::string& fall) const { - std::string fallback=getFallbackString(fall); - if (fallback.empty()) - return false; - else - return stob(fallback); + return getFallbackString(fall) != "0"; } osg::Vec4f Map::getFallbackColour(const std::string& fall) const { - std::string sum=getFallbackString(fall); - if (sum.empty()) - return osg::Vec4f(0.5f,0.5f,0.5f,1.f); - else + std::string sum = getFallbackString(fall); + if (!sum.empty()) { - std::string ret[3]; - unsigned int j=0; - for(unsigned int i=0;i Date: Sat, 29 Dec 2018 15:18:26 +0300 Subject: [PATCH 384/441] Revert to lexical_cast, catch out-of-range exceptions --- components/fallback/fallback.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/components/fallback/fallback.cpp b/components/fallback/fallback.cpp index 70ee2f681..473d10ba4 100644 --- a/components/fallback/fallback.cpp +++ b/components/fallback/fallback.cpp @@ -2,6 +2,8 @@ #include +#include + namespace Fallback { Map::Map(const std::map& fallback):mFallbackMap(fallback) @@ -24,9 +26,11 @@ namespace Fallback { try { - return std::stof(fallback); + // We have to rely on Boost because std::stof from C++11 + // uses the current locale for separators which we don't want and often silently ignores parsing errors. + return boost::lexical_cast(fallback); } - catch (const std::invalid_argument&) + catch (boost::bad_lexical_cast&) { Log(Debug::Error) << "Error: '" << fall << "' setting value (" << fallback << ") is not a valid number, using 0 as a fallback"; } @@ -48,6 +52,10 @@ namespace Fallback { Log(Debug::Error) << "Error: '" << fall << "' setting value (" << fallback << ") is not a valid number, using 0 as a fallback"; } + catch (const std::out_of_range&) + { + Log(Debug::Error) << "Error: '" << fall << "' setting value (" << fallback << ") is out of range, using 0 as a fallback"; + } } return 0; From 8a266803eb7f3f4629ae3de503412e620979c6ab Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 30 Dec 2018 01:27:16 +0300 Subject: [PATCH 385/441] Simplify some world loops --- apps/openmw/mwmechanics/actors.cpp | 5 + apps/openmw/mwworld/worldimp.cpp | 197 ++++++++++------------------- 2 files changed, 75 insertions(+), 127 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 87338c912..4166ece11 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -860,8 +860,13 @@ namespace MWMechanics bool hasSummonEffect = false; for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) + { if (isSummoningEffect(it->first.mId)) + { hasSummonEffect = true; + break; + } + } if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty() || hasSummonEffect) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index cb5ded593..1fff23ad3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -387,10 +387,8 @@ namespace MWWorld void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { // Active cells could have a dirty fog of war, sync it to the CellStore first - for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); - iter!=mWorldScene->getActiveCells().end(); ++iter) + for (CellStore* cellstore : mWorldScene->getActiveCells()) { - CellStore* cellstore = *iter; MWBase::Environment::get().getWindowManager()->writeFog(cellstore); } @@ -546,22 +544,19 @@ namespace MWWorld { // first try named cells const ESM::Cell *cell = mStore.get().searchExtByName (cellName); - if (cell != 0) { + if (cell) return cell; - } // didn't work -> now check for regions - const MWWorld::Store ®ions = mStore.get(); - MWWorld::Store::iterator it = regions.begin(); - for (; it != regions.end(); ++it) + for (const ESM::Region ®ion : mStore.get()) { - if (Misc::StringUtils::ciEqual(cellName, it->mName)) + if (Misc::StringUtils::ciEqual(cellName, region.mName)) { - return mStore.get().searchExtByRegion(it->mId); + return mStore.get().searchExtByRegion(region.mId); } } - return 0; + return nullptr; } const Fallback::Map *World::getFallback() const @@ -692,11 +687,9 @@ namespace MWWorld std::string lowerCaseName = Misc::StringUtils::lowerCase(name); - for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); - iter!=mWorldScene->getActiveCells().end(); ++iter) + for (CellStore* cellstore : mWorldScene->getActiveCells()) { // TODO: caching still doesn't work efficiently here (only works for the one CellStore that the reference is in) - CellStore* cellstore = *iter; Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, false); if (!ptr.isEmpty()) @@ -710,10 +703,8 @@ namespace MWWorld return ret; } - for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); - iter!=mWorldScene->getActiveCells().end(); ++iter) + for (CellStore* cellstore : mWorldScene->getActiveCells()) { - CellStore* cellstore = *iter; Ptr ptr = cellstore->searchInContainer(lowerCaseName); if (!ptr.isEmpty()) return ptr; @@ -770,15 +761,14 @@ namespace MWWorld if (ptr.getContainerStore() == &player.getClass().getContainerStore(player)) return player; - const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); - for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) + for (CellStore* cellstore : mWorldScene->getActiveCells()) { FindContainerVisitor visitor(ptr); - (*cellIt)->forEachType(visitor); + cellstore->forEachType(visitor); if (visitor.mResult.isEmpty()) - (*cellIt)->forEachType(visitor); + cellstore->forEachType(visitor); if (visitor.mResult.isEmpty()) - (*cellIt)->forEachType(visitor); + cellstore->forEachType(visitor); if (!visitor.mResult.isEmpty()) return visitor.mResult; @@ -2535,9 +2525,8 @@ namespace MWWorld std::vector actors; mPhysics->getActorsStandingOn(object, actors); - for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) + for (const Ptr &actor : actors) { - MWWorld::Ptr actor = *it; MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); if (stats.isDead()) continue; @@ -2545,7 +2534,7 @@ namespace MWWorld mPhysics->markAsNonSolid (object); if (actor == getPlayerPtr() && MWBase::Environment::get().getWorld()->getGodModeState()) - return; + continue; MWMechanics::DynamicStat health = stats.getHealth(); health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); @@ -2567,19 +2556,18 @@ namespace MWWorld if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; - std::vector actors; + std::vector actors; mPhysics->getActorsCollidingWith(object, actors); - for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) + for (const Ptr &actor : actors) { - MWWorld::Ptr actor = *it; MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); if (stats.isDead()) continue; mPhysics->markAsNonSolid (object); - if (actor == getPlayerPtr() && MWBase::Environment::get().getWorld()->getGodModeState()) - return; + if (actor == getPlayerPtr() && getGodModeState()) + continue;; MWMechanics::DynamicStat health = stats.getHealth(); health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); @@ -2649,11 +2637,10 @@ namespace MWWorld void World::getContainersOwnedBy (const MWWorld::ConstPtr& owner, std::vector& out) { - const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); - for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) + for (CellStore* cellstore : mWorldScene->getActiveCells()) { GetContainersOwnedByVisitor visitor (owner, out); - (*cellIt)->forEachType(visitor); + cellstore->forEachType(visitor); } } @@ -2671,15 +2658,14 @@ namespace MWWorld void World::getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) { - const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); - for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) + for (CellStore* cellstore : mWorldScene->getActiveCells()) { ListObjectsVisitor visitor; - (*cellIt)->forEach(visitor); + cellstore->forEach(visitor); - for (std::vector::iterator it = visitor.mObjects.begin(); it != visitor.mObjects.end(); ++it) - if (Misc::StringUtils::ciEqual(it->getCellRef().getOwner(), npc.getCellRef().getRefId())) - out.push_back(*it); + for (const Ptr &object : visitor.mObjects) + if (Misc::StringUtils::ciEqual(object.getCellRef().getOwner(), npc.getCellRef().getRefId())) + out.push_back(object); } } @@ -2720,28 +2706,21 @@ namespace MWWorld bool World::findInteriorPosition(const std::string &name, ESM::Position &pos) { - typedef MWWorld::CellRefList::List DoorList; - typedef MWWorld::CellRefList::List StaticList; - pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; pos.pos[0] = pos.pos[1] = pos.pos[2] = 0; MWWorld::CellStore *cellStore = getInterior(name); - if (0 == cellStore) { + if (!cellStore) return false; - } std::vector sortedDoors; - const DoorList &doors = cellStore->getReadOnlyDoors().mList; - for (DoorList::const_iterator it = doors.begin(); it != doors.end(); ++it) + for (const MWWorld::LiveCellRef& door : cellStore->getReadOnlyDoors().mList) { - if (!it->mRef.getTeleport()) - { + if (!door.mRef.getTeleport()) continue; - } - sortedDoors.push_back(&it->mRef); + sortedDoors.push_back(&door.mRef); } // Sort teleporting doors alphabetically, first by ID, then by destination cell to make search consistent @@ -2753,44 +2732,43 @@ namespace MWWorld return lhs->getDestCell() < rhs->getDestCell(); }); - for (std::vector::const_iterator it = sortedDoors.begin(); it != sortedDoors.end(); ++it) + for (const MWWorld::CellRef* door : sortedDoors) { - MWWorld::CellStore *source = 0; + MWWorld::CellStore *source = nullptr; // door to exterior - if ((*it)->getDestCell().empty()) + if (door->getDestCell().empty()) { int x, y; - ESM::Position doorDest = (*it)->getDoorDest(); + ESM::Position doorDest = door->getDoorDest(); positionToIndex(doorDest.pos[0], doorDest.pos[1], x, y); source = getExterior(x, y); } // door to interior else { - source = getInterior((*it)->getDestCell()); + source = getInterior(door->getDestCell()); } - if (0 != source) + if (source) { // Find door leading to our current teleport door // and use its destination to position inside cell. - const DoorList &destinationDoors = source->getReadOnlyDoors().mList; - for (DoorList::const_iterator jt = destinationDoors.begin(); jt != destinationDoors.end(); ++jt) + for (const MWWorld::LiveCellRef& destDoor : source->getReadOnlyDoors().mList) { - if ((*it)->getTeleport() && - Misc::StringUtils::ciEqual(name, jt->mRef.getDestCell())) + if (Misc::StringUtils::ciEqual(name, destDoor.mRef.getDestCell())) { /// \note Using _any_ door pointed to the interior, /// not the one pointed to current door. - pos = jt->mRef.getDoorDest(); + pos = destDoor.mRef.getDoorDest(); return true; } } } } // Fall back to the first static location. - const StaticList &statics = cellStore->getReadOnlyStatics().mList; - if ( statics.begin() != statics.end() ) { + const MWWorld::CellRefList::List &statics = cellStore->getReadOnlyStatics().mList; + if (!statics.empty()) + { pos = statics.begin()->mRef.getPosition(); return true; } @@ -2885,22 +2863,21 @@ namespace MWWorld void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ContentLoader& contentLoader) { - std::vector::const_iterator it(content.begin()); - std::vector::const_iterator end(content.end()); - for (int idx = 0; it != end; ++it, ++idx) + int idx = 0; + for (const std::string &file : content) { - boost::filesystem::path filename(*it); + boost::filesystem::path filename(file); const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); - if (col.doesExist(*it)) + if (col.doesExist(file)) { - contentLoader.load(col.getPath(*it), idx); + contentLoader.load(col.getPath(file), idx); } else { - std::stringstream msg; - msg << "Failed loading " << *it << ": the content file does not exist"; - throw std::runtime_error(msg.str()); + std::string message = "Failed loading " + file + ": the content file does not exist"; + throw std::runtime_error(message); } + idx++; } } @@ -2975,14 +2952,13 @@ namespace MWWorld // For scripted spells we should not use hit contact if (manualSpell) { - // Actors that are targeted by this actor's Follow or Escort packages also side with them if (actor != MWMechanics::getPlayer()) { - for (std::list::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) + for (const MWMechanics::AiPackage* package : stats.getAiSequence()) { - if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast) + if (package->getTypeId() == MWMechanics::AiPackage::TypeIdCast) { - target = (*it)->getTarget(); + target = package->getTarget(); break; } } @@ -3151,17 +3127,13 @@ namespace MWWorld while ( !nextCells.empty() ) { currentCells = nextCells; nextCells.clear(); - for( std::set< std::string >::const_iterator i = currentCells.begin(); i != currentCells.end(); ++i ) { - MWWorld::CellStore *next = getInterior( *i ); + for (const std::string &cell : currentCells) { + MWWorld::CellStore *next = getInterior(cell); if ( !next ) continue; - const MWWorld::CellRefList& doors = next->getReadOnlyDoors(); - const CellRefList::List& refList = doors.mList; - // Check if any door in the cell leads to an exterior directly - for (CellRefList::List::const_iterator it = refList.begin(); it != refList.end(); ++it) + for (const MWWorld::LiveCellRef& ref : next->getReadOnlyDoors().mList) { - const MWWorld::LiveCellRef& ref = *it; if (!ref.mRef.getTeleport()) continue; if (ref.mRef.getDestCell().empty()) @@ -3178,7 +3150,7 @@ namespace MWWorld } } - checkedCells.insert( *i ); + checkedCells.insert(cell); } } @@ -3204,9 +3176,9 @@ namespace MWWorld while ( !nextCells.empty() ) { currentCells = nextCells; nextCells.clear(); - for( std::set< std::string >::const_iterator i = currentCells.begin(); i != currentCells.end(); ++i ) { - MWWorld::CellStore *next = getInterior( *i ); - checkedCells.insert( *i ); + for (const std::string &cell : currentCells) { + MWWorld::CellStore *next = getInterior(cell); + checkedCells.insert(cell); if ( !next ) continue; closestMarker = next->searchConst( id ); @@ -3215,14 +3187,9 @@ namespace MWWorld return closestMarker; } - const MWWorld::CellRefList& doors = next->getReadOnlyDoors(); - const CellRefList::List& doorList = doors.mList; - // Check if any door in the cell leads to an exterior directly - for (CellRefList::List::const_iterator it = doorList.begin(); it != doorList.end(); ++it) + for (const MWWorld::LiveCellRef& ref : next->getReadOnlyDoors().mList) { - const MWWorld::LiveCellRef& ref = *it; - if (!ref.mRef.getTeleport()) continue; if (ref.mRef.getDestCell().empty()) @@ -3248,15 +3215,14 @@ namespace MWWorld std::vector markers; mCells.getExteriorPtrs(id, markers); - for (std::vector::iterator it2 = markers.begin(); it2 != markers.end(); ++it2) + for (const Ptr& marker : markers) { - ESM::Position pos = it2->getRefData().getPosition(); - osg::Vec3f markerPos = pos.asVec3(); + osg::Vec3f markerPos = marker.getRefData().getPosition().asVec3(); float distance = (worldPos - markerPos).length2(); if (distance < closestDistance) { closestDistance = distance; - closestMarker = *it2; + closestMarker = marker; } } @@ -3387,10 +3353,8 @@ namespace MWWorld AddDetectedReferenceVisitor visitor (out, ptr, type, dist*dist); - const Scene::CellStoreCollection& active = mWorldScene->getActiveCells(); - for (Scene::CellStoreCollection::const_iterator it = active.begin(); it != active.end(); ++it) + for (CellStore* cellStore : mWorldScene->getActiveCells()) { - MWWorld::CellStore* cellStore = *it; cellStore->forEach(visitor); } } @@ -3569,27 +3533,8 @@ namespace MWWorld if (ptr == getPlayerPtr() && Settings::Manager::getBool("hit fader", "GUI")) return; - int type = ptr.getClass().getBloodTexture(ptr); - std::string texture; - switch (type) - { - case 2: - texture = getFallback()->getFallbackString("Blood_Texture_2"); - break; - case 1: - texture = getFallback()->getFallbackString("Blood_Texture_1"); - break; - case 0: - default: - texture = getFallback()->getFallbackString("Blood_Texture_0"); - break; - } - - std::stringstream modelName; - modelName << "Blood_Model_"; - int roll = Misc::Rng::rollDice(3); // [0, 2] - modelName << roll; - std::string model = "meshes\\" + getFallback()->getFallbackString(modelName.str()); + std::string texture = getFallback()->getFallbackString("Blood_Texture_" + std::to_string(ptr.getClass().getBloodTexture(ptr))); + std::string model = "meshes\\" + getFallback()->getFallbackString("Blood_Model_" + std::to_string(Misc::Rng::rollDice(3))); // [0, 2] mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); } @@ -3650,13 +3595,13 @@ namespace MWWorld std::vector objects; MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( origin, feetToGameUnits(static_cast(effectIt->mArea)), objects); - for (std::vector::iterator affected = objects.begin(); affected != objects.end(); ++affected) + for (const Ptr& affected : objects) { // Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range. - if (affected->getClass().isActor() && !isActorCollisionEnabled(*affected)) + if (affected.getClass().isActor() && !isActorCollisionEnabled(affected)) continue; - toApply[*affected].push_back(*effectIt); + toApply[affected].push_back(*effectIt); } } @@ -3716,10 +3661,8 @@ namespace MWWorld }; void World::resetActors() { - for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); - iter!=mWorldScene->getActiveCells().end(); ++iter) + for (CellStore* cellstore : mWorldScene->getActiveCells()) { - CellStore* cellstore = *iter; ResetActorsVisitor visitor; cellstore->forEach(visitor); } From a9619dbe036721b05ac1665b2f510714277f9d87 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 30 Dec 2018 17:04:25 +0300 Subject: [PATCH 386/441] Update Fonts documentation --- docs/source/reference/modding/font.rst | 63 +++++++++++++++++++++----- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/docs/source/reference/modding/font.rst b/docs/source/reference/modding/font.rst index 213cdf760..cae320633 100644 --- a/docs/source/reference/modding/font.rst +++ b/docs/source/reference/modding/font.rst @@ -4,9 +4,8 @@ Fonts Morrowind .fnt fonts -------------------- -Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format, -nor compatible with ``.fnt`` formats from any other Bethesda games. To our knowledge, -the format is undocumented and no tools for viewing or editing these fonts exist. +Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format. +To our knowledge, the format is undocumented. OpenMW can load this format and convert it on the fly into something usable (see font loader `source code `_). @@ -16,20 +15,62 @@ In OpenMW 0.32, an --export-fonts command line option was added to write the con TrueType fonts -------------- -Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. -This is the recommended way to create new fonts. +Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. + +0.45.0+ way +----------- +This is the recommended way to install replacement fonts. + +- To replace the primary "Magic Cards" font: + + #. Download `Pelagiad `_ by Isak Larborn (aka Isaskar). + #. Create ``Fonts`` folder at the location of your ``openmw.cfg``. + #. Copy ``openmw_font.xml`` and ``Pelagiad.ttf`` files into the folder. + #. If desired, you can now delete the original ``Magic_Cards.*`` files from your ``Data Files/Fonts`` directory. + #. Pelagiad glyphs may appear to be pretty small, so open ``openmw.cfg`` and adjust the following settings as you deem necessary:: + + [GUI] + font size = 17 + ttf resolution = 96 + +- You can also replace the Daedric font: + + #. Download `Ayembedt `_ by Georg Duffner. + #. Install ``OMWAyembedt.otf`` into the ``Fonts`` folder. + #. Add the following lines into ``openmw_font.xml``:: + + + + + + + + + + + + + + #. This font is missing a few glyphs (mostly punctuation), but it has all the primary glyphs. If desired, you can now delete the original ``daedric.*`` files from your ``Data Files/Fonts`` directory. + +Any Resolution or Size properties in the file have no effect because the engine settings override them. + +The engine automatically takes UI scaling factor into account, so don't account for it when tweaking the settings. + +Pre-0.45.0 way +-------------- - To replace the primary "Magic Cards" font: #. Download `Pelagiad `_ by Isak Larborn (aka Isaskar). #. Install the ``openmw_font.xml`` file into ``resources/mygui/openmw_font.xml`` in your OpenMW installation. #. Copy ``Pelagiad.ttf`` into ``resources/mygui/`` as well. - #. If desired, you can now delete the original ``Magic_Cards.*`` files from your Data Files/Fonts directory. + #. If desired, you can now delete the original ``Magic_Cards.*`` files from your ``Data Files/Fonts`` directory. - You can also replace the Daedric font: #. Download `Ayembedt `_ by Georg Duffner. #. Install ``OMWAyembedt.otf`` into ``/resources/mygui/`` folder in your OpenMW installation. - #. Add the following lines to openmw_font.xml:: + #. Add the following lines to ``openmw_font.xml``:: @@ -45,12 +86,12 @@ This is the recommended way to create new fonts. - #. This font is missing a few glyphs (mostly punctuation), but is complete in the primary glyphs. If desired, you can now delete the original ``daedric.*`` files from your Data Files/Fonts directory. + #. This font is missing a few glyphs (mostly punctuation), but it has all the primary glyphs. If desired, you can now delete the original ``daedric.*`` files from your ``Data Files/Fonts`` directory. - Another replacement for the Daedric font is `Oblivion `_ by Dongle. - #. Install the ``Oblivion.ttf`` file resources/mygui/. - #. The openmw_fonts.xml entry is:: + #. Install the ``Oblivion.ttf`` file into ``resources/mygui/``. + #. The ``openmw_font.xml`` entry is:: @@ -80,7 +121,7 @@ This is the recommended way to create new fonts. Bitmap fonts ------------ -Morrowind ``.fnt`` files are essentially a bitmap font, but using them is discouraged because of no Unicode support. +Morrowind ``.fnt`` files are essentially a bitmap font, but using them is discouraged because they don't have Unicode support. MyGUI has its own format for bitmap fonts. An example can be seen by using the --export-fonts command line option (see above), which converts Morrowind ``.fnt`` to a MyGUI bitmap font. This is the recommended format to use if you wish to edit Morrowind's bitmap font or create a new bitmap font. From ee2560edc3b73949502ab4b4720d2e04262c52b8 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 31 Dec 2018 14:47:00 +0300 Subject: [PATCH 387/441] Fix constant raining --- components/fallback/fallback.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/fallback/fallback.cpp b/components/fallback/fallback.cpp index 473d10ba4..9e64d5fea 100644 --- a/components/fallback/fallback.cpp +++ b/components/fallback/fallback.cpp @@ -63,7 +63,8 @@ namespace Fallback bool Map::getFallbackBool(const std::string& fall) const { - return getFallbackString(fall) != "0"; + std::string fallback = getFallbackString(fall); + return !fallback.empty() && fallback != "0"; } osg::Vec4f Map::getFallbackColour(const std::string& fall) const From 27eb64c7f005c826af3635ce4fa8dea94f21d5f3 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 2 Jan 2019 13:49:04 +0300 Subject: [PATCH 388/441] Clean up fallback record creation again --- apps/openmw/mwworld/esmstore.cpp | 2 -- apps/openmw/mwworld/store.cpp | 52 ----------------------------- apps/openmw/mwworld/worldimp.cpp | 57 +++++++++++++++++++++++++------- 3 files changed, 45 insertions(+), 66 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index c85e3d30e..fe92f570d 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -140,8 +140,6 @@ void ESMStore::setUp(bool validateRecords) mMagicEffects.setUp(); mAttributes.setUp(); mDialogs.setUp(); - mStatics.setUp(); - mDoors.setUp(); if (validateRecords) validate(); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 7df61bec3..67df33bf0 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1053,58 +1053,6 @@ namespace MWWorld } } - template<> - void Store::setUp() - { - // Load default marker definitions, if game files do not have them for some reason - std::pair markers[] = { - std::make_pair("divinemarker", "marker_divine.nif"), - std::make_pair("doormarker", "marker_arrow.nif"), - std::make_pair("northmarker", "marker_north.nif"), - std::make_pair("templemarker", "marker_temple.nif"), - std::make_pair("travelmarker", "marker_travel.nif") - }; - - for (const std::pair &marker : markers) - { - if (search(marker.first) == 0) - { - ESM::Static newMarker; - newMarker.mId = marker.first; - newMarker.mModel = marker.second; - std::pair ret = mStatic.insert(std::make_pair(marker.first, newMarker)); - if (ret.first != mStatic.end()) - { - mShared.push_back(&ret.first->second); - } - } - } - } - - template<> - void Store::setUp() - { - // Load default Door type marker definitions - std::pair markers[] = { - std::make_pair("prisonmarker", "marker_prison.nif") - }; - - for (const std::pair &marker : markers) - { - if (search(marker.first) == 0) - { - ESM::Door newMarker; - newMarker.mId = marker.first; - newMarker.mModel = marker.second; - std::pair ret = mStatic.insert(std::make_pair(marker.first, newMarker)); - if (ret.first != mStatic.end()) - { - mShared.push_back(&ret.first->second); - } - } - } - } - template <> inline RecordId Store::load(ESM::ESMReader &esm) { // The original letter case of a dialogue ID is saved, because it's printed diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index cb5ded593..181fa0742 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -494,6 +494,17 @@ namespace MWWorld gmst["iWereWolfBounty"] = ESM::Variant(1000); gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f); + for (const std::pair ¶ms : gmst) + { + if (!mStore.get().search(params.first)) + { + ESM::GameSetting record; + record.mId = params.first; + record.mValue = params.second; + mStore.insertStatic(record); + } + } + std::map globals; // vanilla Morrowind does not define dayspassed. globals["dayspassed"] = ESM::Variant(1); // but the addons start counting at 1 :( @@ -513,25 +524,47 @@ namespace MWWorld globals["crimegoldturnin"] = ESM::Variant(0); globals["pchasturnin"] = ESM::Variant(0); - for (std::map::iterator it = gmst.begin(); it != gmst.end(); ++it) + for (const std::pair ¶ms : globals) + { + if (!mStore.get().search(params.first)) + { + ESM::Global record; + record.mId = params.first; + record.mValue = params.second; + mStore.insertStatic(record); + } + } + + std::map statics; + // Total conversions from SureAI lack marker records + statics["divinemarker"] = "marker_divine.nif"; + statics["doormarker"] = "marker_arrow.nif"; + statics["northmarker"] = "marker_north.nif"; + statics["templemarker"] = "marker_temple.nif"; + statics["travelmarker"] = "marker_travel.nif"; + + for (const std::pair ¶ms : statics) { - if (!mStore.get().search(it->first)) + if (!mStore.get().search(params.first)) { - ESM::GameSetting setting; - setting.mId = it->first; - setting.mValue = it->second; - mStore.insertStatic(setting); + ESM::Static record; + record.mId = params.first; + record.mModel = params.second; + mStore.insertStatic(record); } } - for (std::map::iterator it = globals.begin(); it != globals.end(); ++it) + std::map doors; + doors["prisonmarker"] = "marker_prison.nif"; + + for (const std::pair ¶ms : doors) { - if (!mStore.get().search(it->first)) + if (!mStore.get().search(params.first)) { - ESM::Global setting; - setting.mId = it->first; - setting.mValue = it->second; - mStore.insertStatic(setting); + ESM::Door record; + record.mId = params.first; + record.mModel = params.second; + mStore.insertStatic(record); } } } From 624db99bf2ba3376076a76eb91a6016f94901544 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 3 Jan 2019 00:31:59 +0300 Subject: [PATCH 389/441] Remove some redundant code --- apps/opencs/model/world/ref.cpp | 4 ---- apps/openmw/mwmechanics/aiwander.cpp | 2 -- apps/openmw/mwmechanics/autocalcspell.cpp | 17 ++--------------- apps/openmw/mwmechanics/autocalcspell.hpp | 5 ----- apps/openmw/mwmechanics/character.cpp | 13 +++---------- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 3 --- apps/openmw/mwmechanics/spellcasting.cpp | 1 - apps/openmw/mwrender/objects.cpp | 2 -- 8 files changed, 5 insertions(+), 42 deletions(-) diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index 0439b9448..b33623590 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -1,9 +1,5 @@ #include "ref.hpp" -#include - -#include - #include "cellcoordinates.hpp" CSMWorld::CellRef::CellRef() : mNew (true) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 837d49f38..d2a57c354 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -1,7 +1,5 @@ #include "aiwander.hpp" -#include - #include #include #include diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index 144449cf0..f55bebfc9 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -240,19 +240,6 @@ namespace MWMechanics return true; } - ESM::Skill::SkillEnum mapSchoolToSkill(int school) - { - std::map schoolSkillMap; // maps spell school to skill id - schoolSkillMap[0] = ESM::Skill::Alteration; - schoolSkillMap[1] = ESM::Skill::Conjuration; - schoolSkillMap[3] = ESM::Skill::Illusion; - schoolSkillMap[2] = ESM::Skill::Destruction; - schoolSkillMap[4] = ESM::Skill::Mysticism; - schoolSkillMap[5] = ESM::Skill::Restoration; - assert(schoolSkillMap.find(school) != schoolSkillMap.end()); - return schoolSkillMap[school]; - } - void calcWeakestSchool (const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation @@ -288,7 +275,7 @@ namespace MWMechanics if (effect.mRange == ESM::RT_Target) x *= 1.5f; - float s = 2.f * actorSkills[mapSchoolToSkill(magicEffect->mData.mSchool)]; + float s = 2.f * actorSkills[spellSchoolToSkill(magicEffect->mData.mSchool)]; if (s - x < minChance) { minChance = s - x; @@ -308,7 +295,7 @@ namespace MWMechanics float skillTerm = 0; if (effectiveSchool != -1) - skillTerm = 2.f * actorSkills[mapSchoolToSkill(effectiveSchool)]; + skillTerm = 2.f * actorSkills[spellSchoolToSkill(effectiveSchool)]; else calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this diff --git a/apps/openmw/mwmechanics/autocalcspell.hpp b/apps/openmw/mwmechanics/autocalcspell.hpp index 6bf3e834b..50be8a5d9 100644 --- a/apps/openmw/mwmechanics/autocalcspell.hpp +++ b/apps/openmw/mwmechanics/autocalcspell.hpp @@ -1,9 +1,6 @@ #ifndef OPENMW_AUTOCALCSPELL_H #define OPENMW_AUTOCALCSPELL_H -#include -#include - #include #include #include @@ -22,8 +19,6 @@ std::vector autoCalcPlayerSpells(const int* actorSkills, const int* bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes); -ESM::Skill::SkillEnum mapSchoolToSkill(int school); - void calcWeakestSchool(const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm); float calcAutoCastChance(const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, int effectiveSchool); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 418962901..553b4e406 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -60,13 +60,6 @@ void wrap(float& rad) rad = std::fmod(rad-osg::PI, 2.0f*osg::PI)+osg::PI; } -std::string toString(int num) -{ - std::ostringstream stream; - stream << num; - return stream.str(); -} - std::string getBestAttack (const ESM::Weapon* weapon) { int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; @@ -235,13 +228,13 @@ public: std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const { int numAnims=0; - while (mAnimation->hasAnimation(prefix + toString(numAnims+1))) + while (mAnimation->hasAnimation(prefix + std::to_string(numAnims+1))) ++numAnims; int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] if (num) *num = roll; - return prefix + toString(roll); + return prefix + std::to_string(roll); } void CharacterController::refreshHitRecoilAnims(CharacterState& idle) @@ -783,7 +776,7 @@ void CharacterController::playDeath(float startpoint, CharacterState death) mCurrentDeath = "deathknockout"; break; default: - mCurrentDeath = "death" + toString(death - CharState_Death1 + 1); + mCurrentDeath = "death" + std::to_string(death - CharState_Death1 + 1); } mDeathState = death; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 43a082348..0a4486928 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1,8 +1,5 @@ #include "mechanicsmanagerimp.hpp" -#include -#include - #include #include diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 7c9e207e9..58d5a85a0 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -1,6 +1,5 @@ #include "spellcasting.hpp" -#include #include #include diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index a0f48ad27..882c9856f 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -1,7 +1,5 @@ #include "objects.hpp" -#include - #include #include From d1e0a101366a0eb82b2e58dce1fc094c95b27eca Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 6 Jan 2019 21:03:19 +0300 Subject: [PATCH 390/441] Make sure AI data is initialized when loading actor records --- components/esm/loadcrea.cpp | 2 +- components/esm/loadnpc.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 83315a5b8..f04439041 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -23,6 +23,7 @@ namespace ESM { mScale = 1.f; mHasAI = false; + mAiData.blank(); bool hasName = false; bool hasNpdt = false; @@ -160,7 +161,6 @@ namespace ESM { mSpells.mList.clear(); mHasAI = false; mAiData.blank(); - mAiData.mServices = 0; mAiPackage.mList.clear(); mTransport.mList.clear(); } diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index dd34934ad..6e7ba66c5 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -18,6 +18,7 @@ namespace ESM mInventory.mList.clear(); mTransport.mList.clear(); mAiPackage.mList.clear(); + mAiData.blank(); mHasAI = false; bool hasName = false; From 7b22d4a25ddfb55f542ba4f3565ff70da5e181b3 Mon Sep 17 00:00:00 2001 From: tackeart Date: Sun, 6 Jan 2019 09:47:17 -0700 Subject: [PATCH 391/441] Instructions on removing tex effect blocks quickly also includes grammar improvement (as suggested by Capostrophic) --- .../modding/texture-modding/convert-bump-mapped-mods.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst index 5fbff38d4..d87e4d244 100644 --- a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst +++ b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst @@ -237,9 +237,9 @@ Since we want the glory of normal mapping in our OpenMW setup, we will go with t #. OpenMW detects normal maps if they have the same name as the base diffuse texture, but with a *_n.dds* suffix. In this mod, the normal maps has a suffix of *_nm.dds*. Change all the files that ends with *_nm.dds* to instead end with *_n.dds*. #. Finally, `we are done`_! -Since these models have one or two textures applied to them, the fix was not that time-consuming. -It gets worse when you have to fix a model that uses loads of textures. The principle is the same, -it just requires more manual work which is annoying and takes time. +Since these models have one or two textures applied to them, the fix was not that time-consuming. The process continues to work for more complex models that use more textures, but looking through each category for texture effects and normal mapped textures rapidly becomes tedious. Luckily, NifSkope provides a feature to do the same automatically. + +Rightclick in NifSkope to access the *Spells* dropdown menu, also available via the top bar, hover over the *Blocks* section, and `choose the action to Remove by ID`_. You can then input the RegEx expression ``^NiTextureEffect`` (directing it to remove any block whose name starts with "NiTextureEffect") to automatically remove all texture effect blocks within the NIF. This also has the helpful side effect of listing `all the blocks within the NIF in the bottom section`_, allowing you to additionally root out any blocks referencing *_nm.dds* textures without having to painstakingly open each category. .. _`Netch Bump mapped`: https://www.nexusmods.com/morrowind/mods/42851/? .. _`Hlaalu Bump mapped`: https://www.nexusmods.com/morrowind/mods/42396/? @@ -256,3 +256,5 @@ it just requires more manual work which is annoying and takes time. .. _Blocks: https://imgur.com/VmQC0WG .. _`no longer have shiny models`: https://imgur.com/vu1k7n1 .. _`we are done`: https://imgur.com/yyZxlTw +.. _`choose the action to Remove by ID`: https://imgur.com/a/qs2t0tC +.. _`all the blocks within the NIF in the bottom section`: https://imgur.com/a/UFFNyWt From 92d7f21926cee660dfaa05ceb4b93c93c0994ed6 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 6 Jan 2019 21:04:24 +0300 Subject: [PATCH 392/441] Don't interrupt compilation when spell/effect functions are used for non-actors --- CHANGELOG.md | 1 + apps/openmw/mwscript/miscextensions.cpp | 12 ++++++++++++ apps/openmw/mwscript/statsextensions.cpp | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da3ba67d..41b57bf28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Bug #4746: Non-solid player can't run or sneak Bug #4750: Sneaking doesn't work in first person view if the player is in attack ready state Bug #4768: Fallback numerical value recovery chokes on invalid arguments + Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken Feature #2229: Improve pathfinding AI Feature #3442: Default values for fallbacks from ini file Feature #3610: Option to invert X axis diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 0cf2fc464..d3b49239c 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -431,6 +431,12 @@ namespace MWScript std::string effect = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); + if (!ptr.getClass().isActor()) + { + runtime.push(0); + return; + } + char *end; long key = strtol(effect.c_str(), &end, 10); if(key < 0 || key > 32767 || *end != '\0') @@ -659,6 +665,12 @@ namespace MWScript std::string id = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); + if (!ptr.getClass().isActor()) + { + runtime.push(0); + return; + } + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); runtime.push(stats.getActiveSpells().isSpellActive(id) || stats.getSpells().isSpellActive(id)); } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 53316c44c..10d1252b9 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -536,7 +536,7 @@ namespace MWScript Interpreter::Type_Integer value = 0; - if (ptr.getClass().getCreatureStats(ptr).getSpells().hasSpell(id)) + if (ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getSpells().hasSpell(id)) value = 1; runtime.push (value); From 02a43d0b321b2b908a355c19e211e6027a772619 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 7 Jan 2019 12:58:47 +0400 Subject: [PATCH 393/441] Do not scale player's model in the 1st-person view depending on race (bug #4383) --- CHANGELOG.md | 1 + apps/openmw/mwclass/npc.cpp | 12 +++++++++++- apps/openmw/mwrender/npcanimation.cpp | 3 ++- apps/openmw/mwworld/worldimp.cpp | 5 ++--- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da3ba67d..f507a14e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Bug #2987: Editor: some chance and AI data fields can overflow Bug #3623: Fix HiDPI on Windows Bug #4411: Reloading a saved game while falling prevents damage in some cases + Bug #4383: Bow model obscures crosshair when arrow is drawn Bug #4540: Rain delay when exiting water Bug #4701: PrisonMarker record is not hardcoded like other markers Bug #4714: Crash upon game load in the repair menu while the "Your repair failed!" message is active diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index ec0671afc..317764e0c 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1172,6 +1172,17 @@ namespace MWClass const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); + // Race weight should not affect 1st-person meshes, otherwise it will change hand proportions and can break aiming. + if (ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson()) + { + if (ref->mBase->isMale()) + scale *= race->mData.mHeight.mMale; + else + scale *= race->mData.mHeight.mFemale; + + return; + } + if (ref->mBase->isMale()) { scale.x() *= race->mData.mWeight.mMale; @@ -1184,7 +1195,6 @@ namespace MWClass scale.y() *= race->mData.mWeight.mFemale; scale.z() *= race->mData.mHeight.mFemale; } - } int Npc::getServices(const MWWorld::ConstPtr &actor) const diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e088728a0..920e09a69 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -317,8 +317,9 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); mViewMode = viewMode; - rebuild(); + MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change + rebuild(); setRenderBin(); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 181fa0742..d66f07d84 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2438,9 +2438,8 @@ namespace MWWorld player.getClass().getInventoryStore(player).setInvListener(anim, player); player.getClass().getInventoryStore(player).setContListener(anim); - scaleObject(getPlayerPtr(), 1.f); // apply race height - - rotateObject(getPlayerPtr(), 0.f, 0.f, 0.f, true); + scaleObject(player, player.getCellRef().getScale()); // apply race height + rotateObject(player, 0.f, 0.f, 0.f, true); MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr()); MWBase::Environment::get().getMechanicsManager()->watchActor(getPlayerPtr()); From ab0841cc3ddcd971438283358211c42d30bc1c3e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 7 Jan 2019 14:27:31 +0400 Subject: [PATCH 394/441] Split physics update from world update in the profiler --- apps/openmw/engine.cpp | 23 +++++++++++++++++++---- apps/openmw/mwbase/world.hpp | 1 + apps/openmw/mwworld/worldimp.cpp | 9 ++++++++- apps/openmw/mwworld/worldimp.hpp | 1 + 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f5b4171d4..15a8f0f32 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -154,14 +154,23 @@ bool OMW::Engine::frame(float frametime) mEnvironment.getStateManager()->endGame(); } + // update physics + osg::Timer_t beforePhysicsTick = osg::Timer::instance()->tick(); + if (mEnvironment.getStateManager()->getState()!= + MWBase::StateManager::State_NoGame) + { + mEnvironment.getWorld()->updatePhysics(frametime, guiActive); + } + osg::Timer_t afterPhysicsTick = osg::Timer::instance()->tick(); + // update world - osg::Timer_t beforePhysicsTick = osg::Timer::instance()->tick();; + osg::Timer_t beforeWorldTick = osg::Timer::instance()->tick(); if (mEnvironment.getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { mEnvironment.getWorld()->update(frametime, guiActive); } - osg::Timer_t afterPhysicsTick = osg::Timer::instance()->tick(); + osg::Timer_t afterWorldTick = osg::Timer::instance()->tick(); // update GUI mEnvironment.getWindowManager()->onFrame(frametime); @@ -180,6 +189,10 @@ bool OMW::Engine::frame(float frametime) stats->setAttribute(frameNumber, "physics_time_taken", osg::Timer::instance()->delta_s(beforePhysicsTick, afterPhysicsTick)); stats->setAttribute(frameNumber, "physics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterPhysicsTick)); + stats->setAttribute(frameNumber, "world_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeWorldTick)); + stats->setAttribute(frameNumber, "world_time_taken", osg::Timer::instance()->delta_s(beforeWorldTick, afterWorldTick)); + stats->setAttribute(frameNumber, "world_time_end", osg::Timer::instance()->delta_s(mStartTick, afterWorldTick)); + if (stats->collectStats("resource")) { mResourceSystem->reportStats(frameNumber, stats); @@ -667,10 +680,12 @@ void OMW::Engine::go() statshandler->addUserStatsLine("Script", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), "script_time_taken", 1000.0, true, false, "script_time_begin", "script_time_end", 10000); - statshandler->addUserStatsLine("Mechanics", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), + statshandler->addUserStatsLine("Mech", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), "mechanics_time_taken", 1000.0, true, false, "mechanics_time_begin", "mechanics_time_end", 10000); - statshandler->addUserStatsLine("Physics", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), + statshandler->addUserStatsLine("Phys", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), "physics_time_taken", 1000.0, true, false, "physics_time_begin", "physics_time_end", 10000); + statshandler->addUserStatsLine("World", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), + "world_time_taken", 1000.0, true, false, "world_time_begin", "world_time_end", 10000); mViewer->addEventHandler(statshandler); diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index dfae5700b..429a12880 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -371,6 +371,7 @@ namespace MWBase /// \return pointer to created record virtual void update (float duration, bool paused) = 0; + virtual void updatePhysics (float duration, bool paused) = 0; virtual void updateWindowManager () = 0; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 181fa0742..4834505dc 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1817,7 +1817,6 @@ namespace MWWorld if (!paused) { - doPhysics (duration); updateNavigator(); } @@ -1837,6 +1836,14 @@ namespace MWWorld } } + void World::updatePhysics (float duration, bool paused) + { + if (!paused) + { + doPhysics (duration); + } + } + void World::updatePlayer() { MWWorld::Ptr player = getPlayerPtr(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 51953cd40..bfd3e918c 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -480,6 +480,7 @@ namespace MWWorld /// \return pointer to created record void update (float duration, bool paused) override; + void updatePhysics (float duration, bool paused) override; void updateWindowManager () override; From 403db9afe328f61686243fd4f486ae0a1f7c0933 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 5 Jan 2019 16:38:42 +0300 Subject: [PATCH 395/441] Don't reset player jumping flag unnecessarily (bug #4775) --- CHANGELOG.md | 1 + apps/openmw/mwphysics/physicssystem.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0d50424c..ff1fb1c2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Bug #4746: Non-solid player can't run or sneak Bug #4750: Sneaking doesn't work in first person view if the player is in attack ready state Bug #4768: Fallback numerical value recovery chokes on invalid arguments + Bug #4775: Slowfall effect resets player jumping flag Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken Feature #2229: Improve pathfinding AI Feature #3442: Default values for fallbacks from ini file diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index a4fd2c1ca..c1a08227e 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1317,6 +1317,7 @@ namespace MWPhysics float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); bool flying = world->isFlying(iter->first); + bool swimming = world->isSwimming(iter->first); bool wasOnGround = physicActor->getOnGround(); osg::Vec3f position = physicActor->getPosition(); @@ -1339,8 +1340,9 @@ namespace MWPhysics float heightDiff = position.z() - oldHeight; MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); - if ((numSteps > 0 && wasOnGround && physicActor->getOnGround()) || flying || world->isSwimming(iter->first) || slowFall < 1) - stats.land(iter->first == player); + bool isStillOnGround = (numSteps > 0 && wasOnGround && physicActor->getOnGround()); + if (isStillOnGround || flying || swimming || slowFall < 1) + stats.land(iter->first == player && (flying || swimming)); else if (heightDiff < 0) stats.addToFallHeight(-heightDiff); From a1af1ff4877935002c03ccceacab1e3c228c58dd Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 7 Jan 2019 17:48:41 +0300 Subject: [PATCH 396/441] Fix extra semicolon, redundant getStore calls and random creature spawn loop --- apps/openmw/mwworld/worldimp.cpp | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1fff23ad3..585d6a614 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -665,10 +665,10 @@ namespace MWWorld if (!cell->getCell()->isExterior() || !cell->getCell()->mName.empty()) return cell->getCell()->mName; - if (const ESM::Region* region = getStore().get().search (cell->getCell()->mRegion)) + if (const ESM::Region* region = mStore.get().search (cell->getCell()->mRegion)) return region->mName; - return getStore().get().find ("sDefaultCellname")->mValue.getString(); + return mStore.get().find ("sDefaultCellname")->mValue.getString(); } void World::removeRefScript (MWWorld::RefData *ref) @@ -972,7 +972,7 @@ namespace MWWorld "sMonthHeartfire", "sMonthFrostfall", "sMonthSunsdusk", "sMonthEveningstar" }; - return getStore().get().find (monthNames[month])->mValue.getString(); + return mStore.get().find (monthNames[month])->mValue.getString(); } TimeStamp World::getTimeStamp() const @@ -1061,7 +1061,7 @@ namespace MWWorld if (mActivationDistanceOverride >= 0) return static_cast(mActivationDistanceOverride); - static const int iMaxActivateDist = getStore().get().find("iMaxActivateDist")->mValue.getInteger(); + static const int iMaxActivateDist = mStore.get().find("iMaxActivateDist")->mValue.getInteger(); return static_cast(iMaxActivateDist); } @@ -1826,7 +1826,7 @@ namespace MWWorld bool swimming = isSwimming(player); bool flying = isFlying(player); - static const float i1stPersonSneakDelta = getStore().get().find("i1stPersonSneakDelta")->mValue.getFloat(); + static const float i1stPersonSneakDelta = mStore.get().find("i1stPersonSneakDelta")->mValue.getFloat(); if (sneaking && !swimming && !flying) mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); else @@ -2533,7 +2533,7 @@ namespace MWWorld mPhysics->markAsNonSolid (object); - if (actor == getPlayerPtr() && MWBase::Environment::get().getWorld()->getGodModeState()) + if (actor == getPlayerPtr() && mGodMode) continue; MWMechanics::DynamicStat health = stats.getHealth(); @@ -2566,8 +2566,8 @@ namespace MWWorld mPhysics->markAsNonSolid (object); - if (actor == getPlayerPtr() && getGodModeState()) - continue;; + if (actor == getPlayerPtr() && mGodMode) + continue; MWMechanics::DynamicStat health = stats.getHealth(); health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); @@ -2893,10 +2893,10 @@ namespace MWWorld if (!selectedSpell.empty()) { - const ESM::Spell* spell = getStore().get().find(selectedSpell); + const ESM::Spell* spell = mStore.get().find(selectedSpell); // Check mana - bool godmode = (isPlayer && getGodModeState()); + bool godmode = (isPlayer && mGodMode); MWMechanics::DynamicStat magicka = stats.getMagicka(); if (magicka.getCurrent() < spell->mData.mCost && !godmode) { @@ -2934,7 +2934,7 @@ namespace MWWorld if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell) stats.getAiSequence().getCombatTargets(targetActors); - const float fCombatDistance = getStore().get().find("fCombatDistance")->mValue.getFloat(); + const float fCombatDistance = mStore.get().find("fCombatDistance")->mValue.getFloat(); osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); @@ -3016,7 +3016,7 @@ namespace MWWorld if (!selectedSpell.empty()) { - const ESM::Spell* spell = getStore().get().find(selectedSpell); + const ESM::Spell* spell = mStore.get().find(selectedSpell); cast.cast(spell); } else if (actor.getClass().hasInventoryStore(actor)) @@ -3389,8 +3389,8 @@ namespace MWWorld int bounty = player.getClass().getNpcStats(player).getBounty(); int playerGold = player.getClass().getContainerStore(player).count(ContainerStore::sGoldId); - float fCrimeGoldDiscountMult = getStore().get().find("fCrimeGoldDiscountMult")->mValue.getFloat(); - float fCrimeGoldTurnInMult = getStore().get().find("fCrimeGoldTurnInMult")->mValue.getFloat(); + static float fCrimeGoldDiscountMult = mStore.get().find("fCrimeGoldDiscountMult")->mValue.getFloat(); + static float fCrimeGoldTurnInMult = mStore.get().find("fCrimeGoldTurnInMult")->mValue.getFloat(); int discount = static_cast(bounty * fCrimeGoldDiscountMult); int turnIn = static_cast(bounty * fCrimeGoldTurnInMult); @@ -3455,7 +3455,7 @@ namespace MWWorld mPlayer->recordCrimeId(); confiscateStolenItems(player); - int iDaysinPrisonMod = getStore().get().find("iDaysinPrisonMod")->mValue.getInteger(); + static int iDaysinPrisonMod = mStore.get().find("iDaysinPrisonMod")->mValue.getInteger(); mDaysInPrison = std::max(1, bounty / iDaysinPrisonMod); return; @@ -3511,18 +3511,18 @@ namespace MWWorld void World::spawnRandomCreature(const std::string &creatureList) { - const ESM::CreatureLevList* list = getStore().get().find(creatureList); + const ESM::CreatureLevList* list = mStore.get().find(creatureList); - int iNumberCreatures = getStore().get().find("iNumberCreatures")->mValue.getInteger(); + static int iNumberCreatures = mStore.get().find("iNumberCreatures")->mValue.getInteger(); int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures] for (int i=0; i::const_iterator effectIt = effects.mList.begin(); effectIt != effects.mList.end(); ++effectIt) { - const ESM::MagicEffect* effect = getStore().get().find(effectIt->mEffectID); + const ESM::MagicEffect* effect = mStore.get().find(effectIt->mEffectID); if (effectIt->mRange != rangeType || (effectIt->mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) continue; // Not right range type, or not area effect and hit an actor @@ -3565,9 +3565,9 @@ namespace MWWorld // Spawn the explosion orb effect const ESM::Static* areaStatic; if (!effect->mArea.empty()) - areaStatic = getStore().get().find (effect->mArea); + areaStatic = mStore.get().find (effect->mArea); else - areaStatic = getStore().get().find ("VFX_DefaultArea"); + areaStatic = mStore.get().find ("VFX_DefaultArea"); std::string texture = effect->mParticle; From b17702bf1f08b9d49168d93eea250864eb690c04 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 25 Dec 2018 20:01:22 +0400 Subject: [PATCH 397/441] Do not restore birthsign abilities upon game load (bug #4329) --- CHANGELOG.md | 3 ++- apps/openmw/mwworld/player.cpp | 8 -------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff1fb1c2a..6ef1e658c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,9 @@ Bug #2987: Editor: some chance and AI data fields can overflow Bug #3623: Fix HiDPI on Windows - Bug #4411: Reloading a saved game while falling prevents damage in some cases + Bug #4329: Removed birthsign abilities are restored after reloading the save Bug #4383: Bow model obscures crosshair when arrow is drawn + Bug #4411: Reloading a saved game while falling prevents damage in some cases Bug #4540: Rain delay when exiting water Bug #4701: PrisonMarker record is not hardcoded like other markers Bug #4714: Crash upon game load in the repair menu while the "Your repair failed!" message is active diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 85181e998..faf15215a 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -414,14 +414,6 @@ namespace MWWorld const ESM::BirthSign* sign = world.getStore().get().search (player.mBirthsign); if (!sign) throw std::runtime_error ("invalid player state record (birthsign does not exist)"); - - // To handle the case where a birth sign was edited in between play sessions (does not yet handle removing the old spells) - // Also needed for ess-imported savegames which do not specify the birtsign spells in the player's spell list. - for (std::vector::const_iterator iter (sign->mPowers.mList.begin()); - iter!=sign->mPowers.mList.end(); ++iter) - { - getPlayer().getClass().getCreatureStats(getPlayer()).getSpells().add (*iter); - } } mCurrentCrimeId = player.mCurrentCrimeId; From 0937f025989eb23910e8a97471c001343c47ef88 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 7 Jan 2019 17:47:39 +0400 Subject: [PATCH 398/441] Get rid of unnecessary string streams --- apps/opencs/model/prefs/state.cpp | 9 +-- apps/opencs/model/tools/search.cpp | 9 +-- apps/opencs/model/world/resources.cpp | 2 +- apps/opencs/view/world/scenesubview.cpp | 4 +- apps/openmw/mwgui/statswindow.cpp | 4 +- apps/openmw/mwgui/tooltips.cpp | 4 +- apps/openmw/mwgui/travelwindow.cpp | 5 +- apps/openmw/mwmechanics/alchemy.cpp | 5 +- apps/openmw/mwrender/landmanager.cpp | 6 +- apps/openmw/mwstate/character.cpp | 6 +- apps/openmw/mwworld/esmstore.hpp | 50 ++++++------- apps/openmw/mwworld/projectilemanager.cpp | 5 +- apps/openmw/mwworld/store.cpp | 85 +++++++++++------------ components/esm/loadcell.cpp | 5 +- components/interpreter/interpreter.cpp | 15 ++-- components/settings/settings.cpp | 73 +++++++------------ 16 files changed, 111 insertions(+), 176 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 57d47ebbc..9bc0e0877 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -3,7 +3,6 @@ #include #include -#include #include "intsetting.hpp" #include "doublesetting.hpp" @@ -393,9 +392,7 @@ CSMPrefs::IntSetting& CSMPrefs::State::declareInt (const std::string& key, if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); - std::ostringstream stream; - stream << default_; - setDefault (key, stream.str()); + setDefault(key, std::to_string(default_)); default_ = mSettings.getInt (key, mCurrentCategory->second.getKey()); @@ -414,9 +411,7 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble (const std::string& key, if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); - std::ostringstream stream; - stream << default_; - setDefault (key, stream.str()); + setDefault(key, std::to_string(default_)); default_ = mSettings.getFloat (key, mCurrentCategory->second.getKey()); diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp index 7cf89f3b5..9f2edfb93 100644 --- a/apps/opencs/model/tools/search.cpp +++ b/apps/opencs/model/tools/search.cpp @@ -77,14 +77,9 @@ void CSMTools::Search::searchRecordStateCell (const CSMWorld::IdTableBase *model { std::vector states = CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); - - std::ostringstream message; - message << states.at (data); - std::ostringstream hint; - hint << "r: " << model->getColumnId (index.column()); - - messages.add (id, message.str(), hint.str()); + const std::string hint = "r: " + model->getColumnId (index.column()); + messages.add (id, states.at(data), hint); } } diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 5bf0267bb..b40ab1389 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -77,7 +77,7 @@ int CSMWorld::Resources::getIndex (const std::string& id) const std::ostringstream stream; stream << "Invalid resource: " << mBaseDirectory << '/' << id; - throw std::runtime_error (stream.str().c_str()); + throw std::runtime_error (stream.str()); } return index; diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index b03cf8fdb..4b129d32a 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -150,10 +150,8 @@ std::string CSVWorld::SceneSubView::getTitle() const void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::UniversalId& id) { setUniversalId(id); - std::ostringstream stream; - stream << "Scene: " << getUniversalId().getId(); - mTitle = stream.str(); + mTitle = "Scene: " + getUniversalId().getId(); setWindowTitle (QString::fromUtf8 (mTitle.c_str())); emit updateTitle(); } diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index e58993a55..df292cfaa 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -125,9 +125,7 @@ namespace MWGui for (int i=0; ids[i]; ++i) if (ids[i]==id) { - std::ostringstream valueString; - valueString << value.getModified(); - setText (id, valueString.str()); + setText (id, std::to_string(value.getModified())); MyGUI::TextBox* box; getWidget(box, id); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index b8a866402..a1879c2f9 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -599,9 +599,7 @@ namespace MWGui std::string ToolTips::toString(const int value) { - std::ostringstream stream; - stream << value; - return stream.str(); + return std::to_string(value); } std::string ToolTips::getWeightString(const float weight, const std::string& prefix) diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 5f5430524..1bcbc2d12 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -87,10 +87,7 @@ namespace MWGui else toAdd->setUserString("interior","n"); - std::ostringstream oss; - oss << price; - toAdd->setUserString("price",oss.str()); - + toAdd->setUserString("price", std::to_string(price)); toAdd->setCaptionWithReplacing("#{sCell=" + name + "} - " + MyGUI::utility::toString(price)+"#{sgp}"); toAdd->setSize(mDestinationsView->getWidth(),lineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel); diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index c199bfb3f..a4434e7db 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -154,9 +154,8 @@ void MWMechanics::Alchemy::updateEffects() if (magicEffect->mData.mBaseCost<=0) { - std::ostringstream os; - os << "invalid base cost for magic effect " << iter->mId; - throw std::runtime_error (os.str()); + const std::string os = "invalid base cost for magic effect " + iter->mId; + throw std::runtime_error (os); } float fPotionT1MagMul = diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index 38c28a72a..e3b19ca47 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -2,8 +2,6 @@ #include -#include - #include #include "../mwbase/environment.hpp" @@ -21,9 +19,7 @@ LandManager::LandManager(int loadFlags) osg::ref_ptr LandManager::getLand(int x, int y) { - std::ostringstream id; - id << x << " " << y; - std::string idstr = id.str(); + std::string idstr = std::to_string(x) + " " + std::to_string(y); osg::ref_ptr obj = mCache->getRefFromObjectCache(idstr); if (obj) diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 4f7d365b4..a8fffaec6 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -58,10 +58,8 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile) int i=0; while (boost::filesystem::exists(slot.mPath)) { - std::ostringstream test; - test << stream.str(); - test << " - " << ++i; - slot.mPath = mPath / (test.str() + ext); + const std::string test = stream.str() + " - " + std::to_string(++i); + slot.mPath = mPath / (test + ext); } slot.mProfile = profile; diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index f4d792118..d170a32c5 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -170,19 +170,19 @@ namespace MWWorld /// Insert a custom record (i.e. with a generated ID that will not clash will pre-existing records) template - const T *insert(const T &x) { - std::ostringstream id; - id << "$dynamic" << mDynamicCount++; + const T *insert(const T &x) + { + const std::string id = "$dynamic" + std::to_string(mDynamicCount++); Store &store = const_cast &>(get()); - if (store.search(id.str()) != 0) { - std::ostringstream msg; - msg << "Try to override existing record '" << id.str() << "'"; - throw std::runtime_error(msg.str()); + if (store.search(id) != 0) + { + const std::string msg = "Try to override existing record '" + id + "'"; + throw std::runtime_error(msg); } T record = x; - record.mId = id.str(); + record.mId = id; T *ptr = store.insert(record); for (iterator it = mStores.begin(); it != mStores.end(); ++it) { @@ -208,15 +208,15 @@ namespace MWWorld } template - const T *insertStatic(const T &x) { - std::ostringstream id; - id << "$dynamic" << mDynamicCount++; + const T *insertStatic(const T &x) + { + const std::string id = "$dynamic" + std::to_string(mDynamicCount++); Store &store = const_cast &>(get()); - if (store.search(id.str()) != 0) { - std::ostringstream msg; - msg << "Try to override existing record '" << id.str() << "'"; - throw std::runtime_error(msg.str()); + if (store.search(id) != 0) + { + const std::string msg = "Try to override existing record '" + id + "'"; + throw std::runtime_error(msg); } T record = x; @@ -247,20 +247,22 @@ namespace MWWorld } template <> - inline const ESM::NPC *ESMStore::insert(const ESM::NPC &npc) { - std::ostringstream id; - id << "$dynamic" << mDynamicCount++; + inline const ESM::NPC *ESMStore::insert(const ESM::NPC &npc) + { + const std::string id = "$dynamic" + std::to_string(mDynamicCount++); - if (Misc::StringUtils::ciEqual(npc.mId, "player")) { + if (Misc::StringUtils::ciEqual(npc.mId, "player")) + { return mNpcs.insert(npc); - } else if (mNpcs.search(id.str()) != 0) { - std::ostringstream msg; - msg << "Try to override existing record '" << id.str() << "'"; - throw std::runtime_error(msg.str()); + } + else if (mNpcs.search(id) != 0) + { + const std::string msg = "Try to override existing record '" + id + "'"; + throw std::runtime_error(msg); } ESM::NPC record = npc; - record.mId = id.str(); + record.mId = id; ESM::NPC *ptr = mNpcs.insert(record); mIds[ptr->mId] = ESM::REC_NPC_; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 4698ba011..ff449acf5 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -109,11 +109,10 @@ namespace if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects { - std::ostringstream ID; - ID << "VFX_Multiple" << effects->mList.size(); + const std::string ID = "VFX_Multiple" + std::to_string(effects->mList.size()); std::vector::iterator it; it = projectileIDs.begin(); - it = projectileIDs.insert(it, ID.str()); + it = projectileIDs.insert(it, ID); } return projectileEffects; } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 67df33bf0..631b6e081 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -9,7 +9,6 @@ #include #include -#include namespace { @@ -102,10 +101,10 @@ namespace MWWorld const T *IndexedStore::find(int index) const { const T *ptr = search(index); - if (ptr == 0) { - std::ostringstream msg; - msg << T::getRecordType() << " with index " << index << " not found"; - throw std::runtime_error(msg.str()); + if (ptr == 0) + { + const std::string msg = T::getRecordType() + " with index " + std::to_string(index) + " not found"; + throw std::runtime_error(msg); } return ptr; } @@ -171,10 +170,10 @@ namespace MWWorld const T *Store::find(const std::string &id) const { const T *ptr = search(id); - if (ptr == 0) { - std::ostringstream msg; - msg << T::getRecordType() << " '" << id << "' not found"; - throw std::runtime_error(msg.str()); + if (ptr == 0) + { + const std::string msg = T::getRecordType() + " '" + id + "' not found"; + throw std::runtime_error(msg); } return ptr; } @@ -184,14 +183,13 @@ namespace MWWorld const T *ptr = searchRandom(id); if(ptr == 0) { - std::ostringstream msg; - msg << T::getRecordType() << " starting with '"< - RecordId Store::load(ESM::ESMReader &esm) + RecordId Store::load(ESM::ESMReader &esm) { T record; bool isDeleted = false; @@ -364,10 +362,10 @@ namespace MWWorld const ESM::LandTexture *Store::find(size_t index, size_t plugin) const { const ESM::LandTexture *ptr = search(index, plugin); - if (ptr == 0) { - std::ostringstream msg; - msg << "Land texture with index " << index << " not found"; - throw std::runtime_error(msg.str()); + if (ptr == 0) + { + const std::string msg = "Land texture with index " + std::to_string(index) + " not found"; + throw std::runtime_error(msg); } return ptr; } @@ -456,10 +454,10 @@ namespace MWWorld const ESM::Land *Store::find(int x, int y) const { const ESM::Land *ptr = search(x, y); - if (ptr == 0) { - std::ostringstream msg; - msg << "Land at (" << x << ", " << y << ") not found"; - throw std::runtime_error(msg.str()); + if (ptr == 0) + { + const std::string msg = "Land at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found"; + throw std::runtime_error(msg); } return ptr; } @@ -591,20 +589,20 @@ namespace MWWorld const ESM::Cell *Store::find(const std::string &id) const { const ESM::Cell *ptr = search(id); - if (ptr == 0) { - std::ostringstream msg; - msg << "Cell '" << id << "' not found"; - throw std::runtime_error(msg.str()); + if (ptr == 0) + { + const std::string msg = "Cell '" + id + "' not found"; + throw std::runtime_error(msg); } return ptr; } const ESM::Cell *Store::find(int x, int y) const { const ESM::Cell *ptr = search(x, y); - if (ptr == 0) { - std::ostringstream msg; - msg << "Exterior at (" << x << ", " << y << ") not found"; - throw std::runtime_error(msg.str()); + if (ptr == 0) + { + const std::string msg = "Exterior at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found"; + throw std::runtime_error(msg); } return ptr; } @@ -784,13 +782,10 @@ namespace MWWorld } ESM::Cell *Store::insert(const ESM::Cell &cell) { - if (search(cell) != 0) { - std::ostringstream msg; - msg << "Failed to create "; - msg << ((cell.isExterior()) ? "exterior" : "interior"); - msg << " cell"; - - throw std::runtime_error(msg.str()); + if (search(cell) != 0) + { + const std::string cellType = (cell.isExterior()) ? "exterior" : "interior"; + throw std::runtime_error("Failed to create " + cellType + " cell"); } ESM::Cell *ptr; if (cell.isExterior()) { @@ -931,9 +926,8 @@ namespace MWWorld const ESM::Pathgrid* pathgrid = search(x,y); if (!pathgrid) { - std::ostringstream msg; - msg << "Pathgrid in cell '" << x << " " << y << "' not found"; - throw std::runtime_error(msg.str()); + const std::string msg = "Pathgrid in cell '" + std::to_string(x) + " " + std::to_string(y) + "' not found"; + throw std::runtime_error(msg); } return pathgrid; } @@ -942,9 +936,8 @@ namespace MWWorld const ESM::Pathgrid* pathgrid = search(name); if (!pathgrid) { - std::ostringstream msg; - msg << "Pathgrid in cell '" << name << "' not found"; - throw std::runtime_error(msg.str()); + const std::string msg = "Pathgrid in cell '" + name + "' not found"; + throw std::runtime_error(msg); } return pathgrid; } @@ -998,10 +991,10 @@ namespace MWWorld const ESM::Attribute *Store::find(size_t index) const { const ESM::Attribute *ptr = search(index); - if (ptr == 0) { - std::ostringstream msg; - msg << "Attribute with index " << index << " not found"; - throw std::runtime_error(msg.str()); + if (ptr == 0) + { + const std::string msg = "Attribute with index " + std::to_string(index) + " not found"; + throw std::runtime_error(msg); } return ptr; } diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 7c4349d31..657d92d6e 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -1,7 +1,6 @@ #include "loadcell.hpp" #include -#include #include #include @@ -209,9 +208,7 @@ namespace ESM } else { - std::ostringstream stream; - stream << mData.mX << ", " << mData.mY; - return stream.str(); + return std::to_string(mData.mX) + ", " + std::to_string(mData.mY); } } diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp index e77a0bb72..b64369e70 100644 --- a/components/interpreter/interpreter.cpp +++ b/components/interpreter/interpreter.cpp @@ -1,7 +1,6 @@ #include "interpreter.hpp" #include -#include #include #include "opcodes.hpp" @@ -116,20 +115,14 @@ namespace Interpreter void Interpreter::abortUnknownCode (int segment, int opcode) { - std::ostringstream error; - - error << "unknown opcode " << opcode << " in segment " << segment; - - throw std::runtime_error (error.str()); + const std::string error = "unknown opcode " + std::to_string(opcode) + " in segment " + std::to_string(segment); + throw std::runtime_error (error); } void Interpreter::abortUnknownSegment (Type_Code code) { - std::ostringstream error; - - error << "opcode outside of the allocated segment range: " << code; - - throw std::runtime_error (error.str()); + const std::string error = "opcode outside of the allocated segment range: " + std::to_string(code); + throw std::runtime_error (error); } void Interpreter::begin() diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 66e5dfc04..27463ea88 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -8,48 +8,6 @@ #include #include -namespace -{ - - bool parseBool(const std::string& string) - { - return (Misc::StringUtils::ciEqual(string, "true")); - } - - float parseFloat(const std::string& string) - { - std::stringstream stream; - stream << string; - float ret = 0.f; - stream >> ret; - return ret; - } - - int parseInt(const std::string& string) - { - std::stringstream stream; - stream << string; - int ret = 0; - stream >> ret; - return ret; - } - - template - std::string toString(T val) - { - std::ostringstream stream; - stream << val; - return stream.str(); - } - - template <> - std::string toString(bool val) - { - return val ? "true" : "false"; - } - -} - namespace Settings { @@ -393,17 +351,36 @@ std::string Manager::getString(const std::string &setting, const std::string &ca float Manager::getFloat (const std::string& setting, const std::string& category) { - return parseFloat( getString(setting, category) ); + const std::string value = getString(setting, category); + try + { + return std::stof(value); + } + catch(const std::exception& e) + { + Log(Debug::Warning) << "Cannot parse setting '" << setting << "' (invalid setting value: " << value << ")."; + return 0; + } } int Manager::getInt (const std::string& setting, const std::string& category) { - return parseInt( getString(setting, category) ); + const std::string value = getString(setting, category); + try + { + return std::stoi(value); + } + catch(const std::exception& e) + { + Log(Debug::Warning) << "Cannot parse setting '" << setting << "' (invalid setting value: " << value << ")."; + return 0; + } } bool Manager::getBool (const std::string& setting, const std::string& category) { - return parseBool( getString(setting, category) ); + const std::string& string = getString(setting, category); + return Misc::StringUtils::ciEqual(string, "true"); } void Manager::setString(const std::string &setting, const std::string &category, const std::string &value) @@ -424,17 +401,17 @@ void Manager::setString(const std::string &setting, const std::string &category, void Manager::setInt (const std::string& setting, const std::string& category, const int value) { - setString(setting, category, toString(value)); + setString(setting, category, std::to_string(value)); } void Manager::setFloat (const std::string &setting, const std::string &category, const float value) { - setString(setting, category, toString(value)); + setString(setting, category, std::to_string(value)); } void Manager::setBool(const std::string &setting, const std::string &category, const bool value) { - setString(setting, category, toString(value)); + setString(setting, category, value ? "true" : "false"); } const CategorySettingVector Manager::apply() From 776c6c2fe62e9c3e1976a8c0a4b5b7bf554ab8d4 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 7 Jan 2019 20:35:36 +0300 Subject: [PATCH 399/441] Fix MSVC warning --- apps/openmw/mwworld/worldimp.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 585d6a614..ef518b8dd 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3127,8 +3127,9 @@ namespace MWWorld while ( !nextCells.empty() ) { currentCells = nextCells; nextCells.clear(); - for (const std::string &cell : currentCells) { - MWWorld::CellStore *next = getInterior(cell); + for (const std::string ¤tCell : currentCells) + { + MWWorld::CellStore *next = getInterior(currentCell); if ( !next ) continue; // Check if any door in the cell leads to an exterior directly @@ -3150,7 +3151,7 @@ namespace MWWorld } } - checkedCells.insert(cell); + checkedCells.insert(currentCell); } } From 3e20f36953aa318de4fa73d3aa126761f2d99147 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 15 Nov 2018 10:09:10 +0400 Subject: [PATCH 400/441] Handle invalid string length for hinting --- apps/opencs/view/world/scriptsubview.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index c9b8127f6..58ad09451 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/data.hpp" @@ -210,18 +212,28 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint) unsigned line = 0, column = 0; char c; std::istringstream stream (hint.c_str()+1); - switch(hint[0]){ + switch(hint[0]) + { case 'R': case 'r': { QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); QString source = mModel->data (index).toString(); + unsigned stringSize = source.length(); unsigned pos, dummy; if (!(stream >> c >> dummy >> pos) ) return; - for (unsigned i = 0; i <= pos; ++i){ - if (source[i] == '\n'){ + if (pos > stringSize) + { + Log(Debug::Warning) << "CSVWorld::ScriptSubView: requested position is higher than actual string length"; + pos = stringSize; + } + + for (unsigned i = 0; i <= pos; ++i) + { + if (source[i] == '\n') + { ++line; column = i+1; } @@ -231,7 +243,7 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint) } case 'l': if (!(stream >> c >> line >> column)) - return; + return; } QTextCursor cursor = mEditor->textCursor(); From 71227ffc0483b883b96baef9ef4eac63abda8991 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 15 Nov 2018 10:21:08 +0400 Subject: [PATCH 401/441] Handle possible invalid iterator issue during settings parsing --- components/config/gamesettings.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 29dbe0391..b20805a11 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -338,6 +338,9 @@ bool Config::GameSettings::writeFileWithComments(QFile &file) if (!comments.empty() && index != -1 && settingRegex.captureCount() >= 2 && mUserSettings.find(settingRegex.cap(1)) != mUserSettings.end()) { + if (commentStart == fileCopy.end()) + throw std::runtime_error("Config::GameSettings: failed to parse settings - iterator is past of end of settings file"); + for (std::vector::const_iterator it = comments.begin(); it != comments.end(); ++it) { *commentStart = *it; From 254f01b89dc681d638e93723df84c68f452fc057 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 8 Jan 2019 20:42:08 +0400 Subject: [PATCH 402/441] RigGeometry optimization: vector iteration is more cheap than map iteration --- components/nifosg/nifloader.cpp | 2 +- components/sceneutil/riggeometry.cpp | 26 +++++++++++++++----------- components/sceneutil/riggeometry.hpp | 9 +++++---- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index eb20b7702..431a2edd1 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1098,7 +1098,7 @@ namespace NifOsg influence.mInvBindMatrix = data->bones[i].trafo.toMatrix(); influence.mBoundSphere = osg::BoundingSpheref(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius); - map->mMap.insert(std::make_pair(boneName, influence)); + map->mData.emplace_back(boneName, influence); } rig->setInfluenceMap(map); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index b086cf628..bf43c2e70 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -136,20 +136,21 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) typedef std::map > Vertex2BoneMap; Vertex2BoneMap vertex2BoneMap; - for (std::map::const_iterator it = mInfluenceMap->mMap.begin(); it != mInfluenceMap->mMap.end(); ++it) + mBoneSphereVector.clear(); + for (auto& influencePair : mInfluenceMap->mData) { - Bone* bone = mSkeleton->getBone(it->first); + Bone* bone = mSkeleton->getBone(influencePair.first); if (!bone) { - Log(Debug::Error) << "Error: RigGeometry did not find bone " << it->first ; + Log(Debug::Error) << "Error: RigGeometry did not find bone " << influencePair.first; continue; } - mBoneSphereMap[bone] = it->second.mBoundSphere; + mBoneSphereVector.emplace_back(bone, influencePair.second.mBoundSphere); - const BoneInfluence& bi = it->second; + const BoneInfluence& bi = influencePair.second; - const std::map& weights = it->second.mWeights; + const std::map& weights = influencePair.second.mWeights; for (std::map::const_iterator weightIt = weights.begin(); weightIt != weights.end(); ++weightIt) { std::vector& vec = vertex2BoneMap[weightIt->first]; @@ -160,11 +161,14 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) } } + Bone2VertexMap bone2VertexMap; for (Vertex2BoneMap::iterator it = vertex2BoneMap.begin(); it != vertex2BoneMap.end(); ++it) { - mBone2VertexMap[it->second].push_back(it->first); + bone2VertexMap[it->second].push_back(it->first); } + mBone2VertexVector.assign(bone2VertexMap.begin(), bone2VertexMap.end()); + return true; } @@ -201,7 +205,7 @@ void RigGeometry::cull(osg::NodeVisitor* nv) osg::Vec3Array* normalDst = static_cast(geom.getNormalArray()); osg::Vec4Array* tangentDst = static_cast(geom.getTexCoordArray(7)); - for (auto &pair : mBone2VertexMap) + for (auto &pair : mBone2VertexVector) { osg::Matrixf resultMat (0, 0, 0, 0, 0, 0, 0, 0, @@ -263,10 +267,10 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) updateGeomToSkelMatrix(nv->getNodePath()); osg::BoundingBox box; - for (BoneSphereMap::const_iterator it = mBoneSphereMap.begin(); it != mBoneSphereMap.end(); ++it) + for (auto& boundPair : mBoneSphereVector) { - Bone* bone = it->first; - osg::BoundingSpheref bs = it->second; + Bone* bone = boundPair.first; + osg::BoundingSpheref bs = boundPair.second; if (mGeomToSkelMatrix) transformBoundingSphere(bone->mMatrixInSkeletonSpace * (*mGeomToSkelMatrix), bs); else diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 60b3edc9d..1d51dfa59 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -36,7 +36,7 @@ namespace SceneUtil struct InfluenceMap : public osg::Referenced { - std::map mMap; + std::vector> mData; }; void setInfluenceMap(osg::ref_ptr influenceMap); @@ -73,12 +73,13 @@ namespace SceneUtil typedef std::vector VertexList; typedef std::map, VertexList> Bone2VertexMap; + typedef std::vector, VertexList>> Bone2VertexVector; - Bone2VertexMap mBone2VertexMap; + Bone2VertexVector mBone2VertexVector; - typedef std::map BoneSphereMap; + typedef std::vector> BoneSphereVector; - BoneSphereMap mBoneSphereMap; + BoneSphereVector mBoneSphereVector; unsigned int mLastFrameNumber; bool mBoundsFirstFrame; From a71cfca58078b3f9dea313635e9c1dfd518ebd70 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 7 Jan 2019 16:05:45 +0300 Subject: [PATCH 403/441] Get rid of some remaining instances of C limit macros --- apps/opencs/view/world/util.cpp | 7 ++++--- apps/openmw/mwworld/cellpreloader.cpp | 3 ++- components/esm/loadland.cpp | 11 ++++++----- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 8cdb2d2db..fef805d56 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -1,5 +1,6 @@ #include "util.hpp" +#include #include #include @@ -214,7 +215,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO case CSMWorld::ColumnBase::Display_UnsignedInteger8: { DialogueSpinBox *sb = new DialogueSpinBox(parent); - sb->setRange(0, UCHAR_MAX); + sb->setRange(0, std::numeric_limits::max()); return sb; } @@ -225,7 +226,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO case CSMWorld::ColumnBase::Display_Float: { DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent); - dsb->setRange(-FLT_MAX, FLT_MAX); + dsb->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); dsb->setSingleStep(0.01f); dsb->setDecimals(3); return dsb; @@ -234,7 +235,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO case CSMWorld::ColumnBase::Display_Double: { DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent); - dsb->setRange(-FLT_MAX, FLT_MAX); + dsb->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); dsb->setSingleStep(0.01f); dsb->setDecimals(6); return dsb; diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 3bf66a5a1..89e5357f0 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -1,6 +1,7 @@ #include "cellpreloader.hpp" #include +#include #include #include @@ -251,7 +252,7 @@ namespace MWWorld { // throw out oldest cell to make room PreloadMap::iterator oldestCell = mPreloadCells.begin(); - double oldestTimestamp = DBL_MAX; + double oldestTimestamp = std::numeric_limits::max(); double threshold = 1.0; // seconds for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it) { diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 9e5e9d07e..722154757 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -1,5 +1,6 @@ #include "loadland.hpp" +#include #include #include "esmreader.hpp" @@ -200,9 +201,9 @@ namespace ESM mLandData->mTextures[i] = 0; for (int i = 0; i < LAND_NUM_VERTS; ++i) { - mLandData->mColours[i*3+0] = -1; - mLandData->mColours[i*3+1] = -1; - mLandData->mColours[i*3+2] = -1; + mLandData->mColours[i*3+0] = 255; + mLandData->mColours[i*3+1] = 255; + mLandData->mColours[i*3+2] = 255; } mLandData->mUnk1 = 0; mLandData->mUnk2 = 0; @@ -252,8 +253,8 @@ namespace ESM if (reader.isNextSub("VHGT")) { VHGT vhgt; if (condLoad(reader, flags, target->mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt))) { - target->mMinHeight = FLT_MAX; - target->mMaxHeight = -FLT_MAX; + target->mMinHeight = std::numeric_limits::max(); + target->mMaxHeight = -std::numeric_limits::max(); float rowOffset = vhgt.mHeightOffset; for (int y = 0; y < LAND_SIZE; y++) { rowOffset += vhgt.mHeightData[y * LAND_SIZE]; From 8e6fd348d136931c8d70d091acb4426bf11ed881 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 9 Jan 2019 21:01:33 +0400 Subject: [PATCH 404/441] RigGeometry optimization: optimize geometry optimization --- components/nifosg/nifloader.cpp | 3 +-- components/sceneutil/riggeometry.cpp | 12 ++++-------- components/sceneutil/riggeometry.hpp | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 431a2edd1..29e6ff4f4 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1092,8 +1092,7 @@ namespace NifOsg const std::vector &weights = data->bones[i].weights; for(size_t j = 0;j < weights.size();j++) { - std::pair indexWeight = std::make_pair(weights[j].vertex, weights[j].weight); - influence.mWeights.insert(indexWeight); + influence.mWeights.emplace_back(weights[j].vertex, weights[j].weight); } influence.mInvBindMatrix = data->bones[i].trafo.toMatrix(); influence.mBoundSphere = osg::BoundingSpheref(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index bf43c2e70..4b550b69c 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -146,18 +146,14 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) continue; } - mBoneSphereVector.emplace_back(bone, influencePair.second.mBoundSphere); - const BoneInfluence& bi = influencePair.second; + mBoneSphereVector.emplace_back(bone, bi.mBoundSphere); - const std::map& weights = influencePair.second.mWeights; - for (std::map::const_iterator weightIt = weights.begin(); weightIt != weights.end(); ++weightIt) + for (auto& weightPair: bi.mWeights) { - std::vector& vec = vertex2BoneMap[weightIt->first]; - - BoneWeight b = std::make_pair(std::make_pair(bone, bi.mInvBindMatrix), weightIt->second); + std::vector& vec = vertex2BoneMap[weightPair.first]; - vec.push_back(b); + vec.emplace_back(std::make_pair(bone, bi.mInvBindMatrix), weightPair.second); } } diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 1d51dfa59..5dd05507c 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -31,7 +31,7 @@ namespace SceneUtil osg::Matrixf mInvBindMatrix; osg::BoundingSpheref mBoundSphere; // - std::map mWeights; + std::vector> mWeights; }; struct InfluenceMap : public osg::Referenced From b78cc4c7d86eb15a7180e58b46b07bdd3e5c46fd Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sun, 13 Jan 2019 20:54:21 +0000 Subject: [PATCH 405/441] Update .gitlab-ci.yml --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6be563e9e..1ac524ce6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,8 +15,8 @@ Debian: - apt-get update -yq - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev # - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - - curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-3_amd64.deb -o libbullet-dev_2.87+dfsg-3_amd64.deb - - curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-3_amd64.deb -o libbullet2.87_2.87+dfsg-3_amd64.deb + - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb + - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb - curl -L https://http.kali.org/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb - curl -L https://http.kali.org/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb - curl -L https://http.kali.org/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb From 8bb270c2bb26264f032dc9e3130637f4b47d54ae Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 14 Jan 2019 00:38:34 +0300 Subject: [PATCH 406/441] Update moved object collisions even if the cell is the same (bug #4800) --- CHANGELOG.md | 1 + apps/openmw/mwworld/worldimp.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ef1e658c..fa5a91a9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Bug #4768: Fallback numerical value recovery chokes on invalid arguments Bug #4775: Slowfall effect resets player jumping flag Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken + Bug #4800: Standing collisions are not updated immediately when an object is teleported without a cell change Feature #2229: Improve pathfinding AI Feature #3442: Default values for fallbacks from ini file Feature #3610: Option to invert X axis diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8430b8897..57556e1cb 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1314,7 +1314,10 @@ namespace MWWorld { mRendering->moveObject(newPtr, vec); if (movePhysics) + { mPhysics->updatePosition(newPtr); + mPhysics->updatePtr(ptr, newPtr); + } } if (isPlayer) { From f6e1aaae1a1c86f2754c22797832b5642f136d66 Mon Sep 17 00:00:00 2001 From: Artur Rojek Date: Tue, 15 Jan 2019 13:24:59 +0100 Subject: [PATCH 407/441] Use case-insensitive compare when searching for game data by the wizard. Until now, the wizard was only comparing against an exact file name match while searching for game data, eg. "data*.cab". This patch makes it possible to locate game data regardless of case sensitivity, eg. "Data*.cab". --- apps/wizard/unshield/unshieldworker.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 9cdb4cd78..7aa84d3b1 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -856,7 +856,7 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt if (info.isDir()) { if (directories) { - if (info.fileName() == fileName) { + if (!info.fileName().compare(fileName, Qt::CaseInsensitive)) { result.append(info.absoluteFilePath()); } else { if (recursive) @@ -872,11 +872,11 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt switch (flags) { case Qt::MatchExactly: - if (info.fileName() == fileName) + if (!info.fileName().compare(fileName, Qt::CaseInsensitive)) result.append(info.absoluteFilePath()); break; case Qt::MatchEndsWith: - if (info.fileName().endsWith(fileName)) + if (info.fileName().endsWith(fileName), Qt::CaseInsensitive) result.append(info.absoluteFilePath()); break; } From 166d757a196f8ab56992821934cf71f7f6496942 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 16 Jan 2019 00:08:42 +0300 Subject: [PATCH 408/441] Allow stray special characters before a begin statement --- CHANGELOG.md | 1 + components/compiler/fileparser.cpp | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa5a91a9d..44e6aa3c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Bug #4775: Slowfall effect resets player jumping flag Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken Bug #4800: Standing collisions are not updated immediately when an object is teleported without a cell change + Bug #4803: Stray special characters before begin statement break script compilation Feature #2229: Improve pathfinding AI Feature #3442: Default values for fallbacks from ini file Feature #3610: Option to invert X axis diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index c9e205b8a..8b2f1fe08 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -94,14 +94,16 @@ namespace Compiler bool FileParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { - if (code==Scanner::S_newline) + // Ignore any junk special characters + if (mState == BeginState) { - if (mState==BeginState) - { - // ignore empty lines - return true; - } + if (code != Scanner::S_newline) + reportWarning ("Ignoring stray special character before begin statement", loc); + return true; + } + if (code==Scanner::S_newline) + { if (mState==BeginCompleteState) { // parse the script body From 39c4a7833c434d3bd9739245a62c828fcbe1c815 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 16 Jan 2019 22:00:50 +0400 Subject: [PATCH 409/441] Fix out of range exception when try to load a particle node with empty sizes array (bug #4804) --- CHANGELOG.md | 1 + components/nifosg/nifloader.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44e6aa3c6..0fda600fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken Bug #4800: Standing collisions are not updated immediately when an object is teleported without a cell change Bug #4803: Stray special characters before begin statement break script compilation + Bug #4804: Particle system with the "Has Sizes = false" causes an exception Feature #2229: Improve pathfinding AI Feature #3442: Default values for fallbacks from ini file Feature #3610: Option to invert X axis diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 29e6ff4f4..08f6eb96c 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -822,7 +822,9 @@ namespace NifOsg if (particle.vertex < int(particledata->colors.size())) partcolor = particledata->colors.at(particle.vertex); - float size = particledata->sizes.at(particle.vertex) * partctrl->size; + float size = partctrl->size; + if (particle.vertex < int(particledata->sizes.size())) + size *= particledata->sizes.at(particle.vertex); created->setSizeRange(osgParticle::rangef(size, size)); box.expandBy(osg::BoundingSphere(position, size)); From 25650e65bf0e68960928f041e0ea26ad230692d0 Mon Sep 17 00:00:00 2001 From: Azdul Date: Wed, 16 Jan 2019 20:19:15 +0100 Subject: [PATCH 410/441] Read any BSA file - detection based on version in BSA header --- CMakeLists.txt | 3 +- components/CMakeLists.txt | 5 +- components/bsa/bsa_file.cpp | 34 +-- components/bsa/bsa_file.hpp | 30 ++- components/bsa/memorystream.cpp | 48 ++++ components/bsa/memorystream.hpp | 62 +++++ components/bsa/tes4bsa_file.cpp | 434 +++++++++++++++++++++++++++++++ components/bsa/tes4bsa_file.hpp | 108 ++++++++ components/vfs/bsaarchive.cpp | 22 +- components/vfs/bsaarchive.hpp | 7 +- extern/bsaopthash/CMakeLists.txt | 17 ++ extern/bsaopthash/hash.cpp | 108 ++++++++ extern/bsaopthash/hash.hpp | 42 +++ 13 files changed, 881 insertions(+), 39 deletions(-) create mode 100644 components/bsa/memorystream.cpp create mode 100644 components/bsa/memorystream.hpp create mode 100644 components/bsa/tes4bsa_file.cpp create mode 100644 components/bsa/tes4bsa_file.hpp create mode 100644 extern/bsaopthash/CMakeLists.txt create mode 100644 extern/bsaopthash/hash.cpp create mode 100644 extern/bsaopthash/hash.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f34e3c83c..1c54376c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -296,7 +296,7 @@ if(QT_STATIC) endif() -set(BOOST_COMPONENTS system filesystem program_options) +set(BOOST_COMPONENTS system filesystem program_options iostreams zlib) if(WIN32) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) endif(WIN32) @@ -610,6 +610,7 @@ add_subdirectory (extern/oics) if (BUILD_OPENCS) add_subdirectory (extern/osgQt) endif() +add_subdirectory (extern/bsaopthash) # Components add_subdirectory (components) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7a88dc18e..7ab08292e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -33,7 +33,7 @@ add_component_dir (settings ) add_component_dir (bsa - bsa_file + bsa_file tes4bsa_file memorystream ) add_component_dir (vfs @@ -218,6 +218,8 @@ target_link_libraries(components ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_IOSTREAMS_LIBRARY} + ${Boost_ZLIB_LIBRARY} ${OSG_LIBRARIES} ${OPENTHREADS_LIBRARIES} ${OSGPARTICLE_LIBRARIES} @@ -231,6 +233,7 @@ target_link_libraries(components ${SDL2_LIBRARIES} ${OPENGL_gl_LIBRARY} ${MyGUI_LIBRARIES} + ${BSAOPTHASH_LIBRARIES} RecastNavigation::DebugUtils RecastNavigation::Detour RecastNavigation::Recast diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 8905a86a1..abeca5326 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -35,7 +35,7 @@ using namespace Bsa; /// Error handling void BSAFile::fail(const string &msg) { - throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + filename); + throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename); } /// Read header information from the input source @@ -71,10 +71,10 @@ void BSAFile::readHeader() * the beginning of this buffer. * */ - assert(!isLoaded); + assert(!mIsLoaded); namespace bfs = boost::filesystem; - bfs::ifstream input(bfs::path(filename), std::ios_base::binary); + bfs::ifstream input(bfs::path(mFilename), std::ios_base::binary); // Total archive size std::streamoff fsize = 0; @@ -117,8 +117,8 @@ void BSAFile::readHeader() input.read(reinterpret_cast(&offsets[0]), 12*filenum); // Read the string table - stringBuf.resize(dirsize-12*filenum); - input.read(&stringBuf[0], stringBuf.size()); + mStringBuf.resize(dirsize-12*filenum); + input.read(&mStringBuf[0], mStringBuf.size()); // Check our position assert(input.tellg() == std::streampos(12+dirsize)); @@ -129,40 +129,40 @@ void BSAFile::readHeader() size_t fileDataOffset = 12 + dirsize + 8*filenum; // Set up the the FileStruct table - files.resize(filenum); + mFiles.resize(filenum); for(size_t i=0;i fsize) fail("Archive contains offsets outside itself"); // Add the file name to the lookup - lookup[fs.name] = i; + mLookup[fs.name] = i; } - isLoaded = true; + mIsLoaded = true; } /// Get the index of a given file name, or -1 if not found int BSAFile::getIndex(const char *str) const { - Lookup::const_iterator it = lookup.find(str); - if(it == lookup.end()) + Lookup::const_iterator it = mLookup.find(str); + if(it == mLookup.end()) return -1; int res = it->second; - assert(res >= 0 && (size_t)res < files.size()); + assert(res >= 0 && (size_t)res < mFiles.size()); return res; } /// Open an archive file. void BSAFile::open(const string &file) { - filename = file; + mFilename = file; readHeader(); } @@ -173,12 +173,12 @@ Files::IStreamPtr BSAFile::getFile(const char *file) if(i == -1) fail("File not found: " + string(file)); - const FileStruct &fs = files[i]; + const FileStruct &fs = mFiles[i]; - return Files::openConstrainedFileStream (filename.c_str (), fs.offset, fs.fileSize); + return Files::openConstrainedFileStream (mFilename.c_str (), fs.offset, fs.fileSize); } Files::IStreamPtr BSAFile::getFile(const FileStruct *file) { - return Files::openConstrainedFileStream (filename.c_str (), file->offset, file->fileSize); + return Files::openConstrainedFileStream (mFilename.c_str (), file->offset, file->fileSize); } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 196dc30fb..d12d01b0c 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -56,18 +56,18 @@ public: }; typedef std::vector FileList; -private: +protected: /// Table of files in this archive - FileList files; + FileList mFiles; /// Filename string buffer - std::vector stringBuf; + std::vector mStringBuf; /// True when an archive has been loaded - bool isLoaded; + bool mIsLoaded; /// Used for error messages - std::string filename; + std::string mFilename; /// Case insensitive string comparison struct iltstr @@ -81,13 +81,16 @@ private: checks are case insensitive. */ typedef std::map Lookup; - Lookup lookup; + Lookup mLookup; /// Error handling void fail(const std::string &msg); /// Read header information from the input source - void readHeader(); + virtual void readHeader(); + + /// Read header information from the input source + /// Get the index of a given file name, or -1 if not found /// @note Thread safe. @@ -100,7 +103,10 @@ public: */ BSAFile() - : isLoaded(false) + : mIsLoaded(false) + { } + + virtual ~BSAFile() { } /// Open an archive file. @@ -112,24 +118,24 @@ public: */ /// Check if a file exists - bool exists(const char *file) const + virtual bool exists(const char *file) const { return getIndex(file) != -1; } /** Open a file contained in the archive. Throws an exception if the file doesn't exist. * @note Thread safe. */ - Files::IStreamPtr getFile(const char *file); + virtual Files::IStreamPtr getFile(const char *file); /** Open a file contained in the archive. * @note Thread safe. */ - Files::IStreamPtr getFile(const FileStruct* file); + virtual Files::IStreamPtr getFile(const FileStruct* file); /// Get a list of all files /// @note Thread safe. const FileList &getList() const - { return files; } + { return mFiles; } }; } diff --git a/components/bsa/memorystream.cpp b/components/bsa/memorystream.cpp new file mode 100644 index 000000000..3cacad60f --- /dev/null +++ b/components/bsa/memorystream.cpp @@ -0,0 +1,48 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (memorystream.cpp) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + TES4 stuff upgrade added by Azdul 2019 + + */ +#include "memorystream.hpp" + + +namespace Bsa +{ +MemoryInputStreamBuf::MemoryInputStreamBuf(size_t bufferSize) : mBufferPtr(bufferSize) +{ + this->setg(mBufferPtr.data(), mBufferPtr.data(), mBufferPtr.data() + bufferSize); +} + +char* MemoryInputStreamBuf::getRawData() { + return mBufferPtr.data(); +} + +MemoryInputStream::MemoryInputStream(size_t bufferSize) : + MemoryInputStreamBuf(bufferSize), + std::istream(static_cast(this)) { + +} + +char* MemoryInputStream::getRawData() { + return MemoryInputStreamBuf::getRawData(); +} +} diff --git a/components/bsa/memorystream.hpp b/components/bsa/memorystream.hpp new file mode 100644 index 000000000..aed39e6d6 --- /dev/null +++ b/components/bsa/memorystream.hpp @@ -0,0 +1,62 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (memorystream.h) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + TES4 stuff upgrade added by Azdul 2019 + + */ + +#ifndef BSA_MEMORY_STREAM_H +#define BSA_MEMORY_STREAM_H + +#include +#include + +namespace Bsa +{ +/** +Class used internally by MemoryInputStream. +*/ +class MemoryInputStreamBuf : public std::streambuf { + +public: + MemoryInputStreamBuf(size_t bufferSize); + char* getRawData(); +private: + //correct call to delete [] on C++ 11 + std::vector mBufferPtr; +}; + +/** + Class replaces Ogre memory streams without introducing any new external dependencies + beyond standard library. + + Allows to pass memory buffer as Files::IStreamPtr. + + Memory buffer is freed once the class instance is destroyed. + */ +class MemoryInputStream : virtual MemoryInputStreamBuf, std::istream { +public: + MemoryInputStream(size_t bufferSize); + char* getRawData(); +}; + +} +#endif diff --git a/components/bsa/tes4bsa_file.cpp b/components/bsa/tes4bsa_file.cpp new file mode 100644 index 000000000..5c36061d5 --- /dev/null +++ b/components/bsa/tes4bsa_file.cpp @@ -0,0 +1,434 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (tes4bsa_file.cpp) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + TES4 stuff added by cc9cii 2018 + + */ +#include "tes4bsa_file.hpp" + +#include +#include +#include +#include +#include +#include + +#include // see: http://en.uesp.net/wiki/Tes4Mod:Hash_Calculation +#include +#include +#include +#include +#include +#include +#include + +namespace Bsa +{ + +//special marker for invalid records, +//equal to max uint32_t value +const uint32_t TES4BSAFile::sInvalidOffset = std::numeric_limits::max(); + +//bit marking compression on file size +const uint32_t TES4BSAFile::sCompressedFlag = 1u << 30u; + + +TES4BSAFile::FileRecord::FileRecord() : size(0), offset(sInvalidOffset) +{ } + +bool TES4BSAFile::FileRecord::isValid() const +{ + return offset != sInvalidOffset; +} + +bool TES4BSAFile::FileRecord::isCompressed(bool bsaCompressedByDefault) const +{ + bool compressionFlagEnabled = ((size & sCompressedFlag) == sCompressedFlag); + + if (bsaCompressedByDefault) { + return !compressionFlagEnabled; + } + return compressionFlagEnabled; +} + +std::uint32_t TES4BSAFile::FileRecord::getSizeWithoutCompressionFlag() const { + return size & (~sCompressedFlag); +} + +TES4BSAFile::TES4BSAFile() + : mCompressedByDefault(false), mEmbeddedFileNames(false) +{ } + +TES4BSAFile::~TES4BSAFile() +{ } + +void TES4BSAFile::getBZString(std::string& str, std::istream& filestream) +{ + char size = 0; + filestream.read(&size, 1); + + boost::scoped_array buf(new char[size]); + filestream.read(buf.get(), size); + + if (buf[size - 1] != 0) + { + str.assign(buf.get(), size); + if (str.size() != ((size_t)size)) { + fail("getBZString string size mismatch"); + } + } + else + { + str.assign(buf.get(), size - 1); // don't copy null terminator + if (str.size() != ((size_t)size - 1)) { + fail("getBZString string size mismatch (null terminator)"); + } + } + + return; +} + +/// Read header information from the input source +void TES4BSAFile::readHeader() +{ + assert(!mIsLoaded); + + namespace bfs = boost::filesystem; + bfs::ifstream input(bfs::path(mFilename), std::ios_base::binary); + + // Total archive size + std::streamoff fsize = 0; + if (input.seekg(0, std::ios_base::end)) + { + fsize = input.tellg(); + input.seekg(0); + } + + if (fsize < 36) // header is 36 bytes + fail("File too small to be a valid BSA archive"); + + // Get essential header numbers + //size_t dirsize, filenum; + std::uint32_t archiveFlags, folderCount, fileCount, totalFileNameLength; + { + // First 36 bytes + std::uint32_t header[9]; + + input.read(reinterpret_cast(header), 36); + + if (header[0] != 0x00415342 /*"BSA\x00"*/ || (header[1] != 0x67 /*TES4*/ && header[1] != 0x68 /*TES5*/)) + fail("Unrecognized TES4 BSA header"); + + // header[2] is offset, should be 36 = 0x24 which is the size of the header + + // Oblivion - Meshes.bsa + // + // 0111 1000 0111 = 0x0787 + // ^^^ ^ ^^^ + // ||| | ||+-- has names for dirs (mandatory?) + // ||| | |+--- has names for files (mandatory?) + // ||| | +---- files are compressed by default + // ||| | + // ||| +---------- unknown (TES5: retain strings during startup) + // ||+------------ unknown (TES5: embedded file names) + // |+------------- unknown + // +-------------- unknown + // + archiveFlags = header[3]; + folderCount = header[4]; + fileCount = header[5]; + totalFileNameLength = header[7]; + + mCompressedByDefault = (archiveFlags & 0x4) != 0; + mEmbeddedFileNames = header[1] == 0x68 /*TES5*/ && (archiveFlags & 0x100) != 0; + } + + // folder records + std::uint64_t hash; + FolderRecord fr; + for (std::uint32_t i = 0; i < folderCount; ++i) + { + input.read(reinterpret_cast(&hash), 8); + input.read(reinterpret_cast(&fr.count), 4); + input.read(reinterpret_cast(&fr.offset), 4); + + std::map::const_iterator lb = mFolders.lower_bound(hash); + if (lb != mFolders.end() && !(mFolders.key_comp()(hash, lb->first))) + fail("Archive found duplicate folder name hash"); + else + mFolders.insert(lb, std::pair(hash, fr)); + } + + // file record blocks + std::uint64_t fileHash; + FileRecord file; + + std::string folder(""); + std::uint64_t folderHash; + if ((archiveFlags & 0x1) == 0) + { + folderCount = 1; + } + + mFiles.clear(); + std::vector fullPaths; + + for (std::uint32_t i = 0; i < folderCount; ++i) + { + if ((archiveFlags & 0x1) != 0) + getBZString(folder, input); + + std::string emptyString; + folderHash = GenOBHash(folder, emptyString); + + std::map::iterator iter = mFolders.find(folderHash); + if (iter == mFolders.end()) + fail("Archive folder name hash not found"); + + for (std::uint32_t j = 0; j < iter->second.count; ++j) + { + input.read(reinterpret_cast(&fileHash), 8); + input.read(reinterpret_cast(&file.size), 4); + input.read(reinterpret_cast(&file.offset), 4); + + std::map::const_iterator lb = iter->second.files.lower_bound(fileHash); + if (lb != iter->second.files.end() && !(iter->second.files.key_comp()(fileHash, lb->first))) + { + fail("Archive found duplicate file name hash"); + } + + iter->second.files.insert(lb, std::pair(fileHash, file)); + + FileStruct fileStruct; + fileStruct.fileSize = file.getSizeWithoutCompressionFlag(); + fileStruct.offset = file.offset; + fileStruct.name = nullptr; + mFiles.push_back(fileStruct); + + fullPaths.push_back(folder); + } + } + + // file record blocks + if ((archiveFlags & 0x2) != 0) + { + mStringBuf.resize(totalFileNameLength); + input.read(&mStringBuf[0], mStringBuf.size()); + } + + size_t mStringBuffOffset = 0; + size_t totalStringsSize = 0; + for (std::uint32_t fileIndex = 0; fileIndex < mFiles.size(); ++fileIndex) { + + if (mStringBuffOffset >= totalFileNameLength) { + fail("Corrupted names record in BSA file"); + } + + //The vector guarantees that its elements occupy contiguous memory + mFiles[fileIndex].name = reinterpret_cast(mStringBuf.data() + mStringBuffOffset); + + fullPaths.at(fileIndex) += "\\" + std::string(mStringBuf.data() + mStringBuffOffset); + + while (mStringBuffOffset < totalFileNameLength) { + if (mStringBuf[mStringBuffOffset] != '\0') { + mStringBuffOffset++; + } + else { + mStringBuffOffset++; + break; + } + } + //we want to keep one more 0 character at the end of each string + totalStringsSize += fullPaths.at(fileIndex).length() + 1u; + } + mStringBuf.resize(totalStringsSize); + + mStringBuffOffset = 0; + for (std::uint32_t fileIndex = 0u; fileIndex < mFiles.size(); fileIndex++) { + size_t stringLength = fullPaths.at(fileIndex).length(); + + std::copy(fullPaths.at(fileIndex).c_str(), + //plus 1 because we also want to copy 0 at the end of the string + fullPaths.at(fileIndex).c_str() + stringLength + 1u, + mStringBuf.data() + mStringBuffOffset); + + mFiles[fileIndex].name = reinterpret_cast(mStringBuf.data() + mStringBuffOffset); + + mLookup[reinterpret_cast(mStringBuf.data() + mStringBuffOffset)] = fileIndex; + mStringBuffOffset += stringLength + 1u; + } + + if (mStringBuffOffset != mStringBuf.size()) { + fail("Could not resolve names of files in BSA file"); + } + + convertCompressedSizesToUncompressed(); + mIsLoaded = true; +} + +TES4BSAFile::FileRecord TES4BSAFile::getFileRecord(const std::string& filePath) const +{ + boost::filesystem::path p(filePath); + std::string stem = p.stem().string(); + std::string ext = p.extension().string(); + std::string filename = p.filename().string(); + p.remove_filename(); + + std::string folder = p.string(); + // GenOBHash already converts to lowercase and replaces file separators but not for path + boost::algorithm::to_lower(folder); + std::replace(folder.begin(), folder.end(), '/', '\\'); + + std::string emptyString; + std::uint64_t folderHash = GenOBHash(folder, emptyString); + + std::map::const_iterator it = mFolders.find(folderHash); + if (it == mFolders.end()) + { + return FileRecord(); // folder not found, return default which has offset of -1 + } + + boost::algorithm::to_lower(stem); + boost::algorithm::to_lower(ext); + std::uint64_t fileHash = GenOBHashPair(stem, ext); + std::map::const_iterator fileInDirIter = it->second.files.find(fileHash); + if (fileInDirIter == it->second.files.end()) + { + return FileRecord(); // file not found, return default which has offset of -1 + } + + return fileInDirIter->second; +} + +Files::IStreamPtr TES4BSAFile::getFile(const FileStruct* file) { + + FileRecord fileRec = getFileRecord(file->name); + return getFile(fileRec); +} + +Files::IStreamPtr TES4BSAFile::getFile(const char* file) +{ + FileRecord fileRec = getFileRecord(file); + if (!fileRec.isValid()) { + fail("File not found: " + std::string(file)); + } + + return getFile(fileRec); +} + +Files::IStreamPtr TES4BSAFile::getFile(const FileRecord& fileRecord) { + + if (fileRecord.isCompressed(mCompressedByDefault)) { + Files::IStreamPtr streamPtr = Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.getSizeWithoutCompressionFlag()); + + std::istream* fileStream = streamPtr.get(); + + if (mEmbeddedFileNames) { + std::string embeddedFileName; + getBZString(embeddedFileName,*fileStream); + } + + uint32_t uncompressedSize = 0u; + fileStream->read(reinterpret_cast(&uncompressedSize), sizeof(uncompressedSize)); + + boost::iostreams::filtering_streambuf inputStreamBuf; + inputStreamBuf.push(boost::iostreams::zlib_decompressor()); + inputStreamBuf.push(*fileStream); + + std::shared_ptr > bufferVec = std::make_shared>(uncompressedSize); + Bsa::MemoryInputStream* result = new MemoryInputStream(uncompressedSize); + + boost::iostreams::basic_array_sink sr(result->getRawData(), uncompressedSize); + boost::iostreams::copy(inputStreamBuf, sr); + std::istream* pPtr = (std::istream*) result; + return std::shared_ptr(pPtr); + } + + return Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.size); +} + +BsaVersion TES4BSAFile::detectVersion(std::string filePath) +{ + namespace bfs = boost::filesystem; + bfs::ifstream input(bfs::path(filePath), std::ios_base::binary); + + // Total archive size + std::streamoff fsize = 0; + if (input.seekg(0, std::ios_base::end)) + { + fsize = input.tellg(); + input.seekg(0); + } + + if (fsize < 12) { + return BSAVER_UNKNOWN; + } + + + // Get essential header numbers + size_t dirsize, filenum; + { + // First 12 bytes + uint32_t head[3]; + + input.read(reinterpret_cast(head), 12); + + if (head[0] == static_cast(BSAVER_TES3)) { + return BSAVER_TES3; + } + + if (head[0] = static_cast(BSAVER_TES4PLUS)) { + return BSAVER_TES4PLUS; + } + } + return BSAVER_UNKNOWN; +} + +//mFiles used by OpenMW expects uncompressed sizes +void TES4BSAFile::convertCompressedSizesToUncompressed() +{ + for (auto iter = mFiles.begin(); iter != mFiles.end(); ++iter) + { + const FileRecord& fileRecord = getFileRecord(iter->name); + if (!fileRecord.isValid()) + { + fail("Could not find file " + std::string(iter->name) + " in BSA"); + } + + if (!fileRecord.isCompressed(mCompressedByDefault)) + { + //no need to fix fileSize in mFiles - uncompressed size already set + continue; + } + + Files::IStreamPtr dataBegin = Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.getSizeWithoutCompressionFlag()); + + if (mEmbeddedFileNames) + { + std::string embeddedFileName; + getBZString(embeddedFileName, *(dataBegin.get())); + } + + dataBegin->read(reinterpret_cast(&(iter->fileSize)), sizeof(iter->fileSize)); + } +} + +} //namespace Bsa diff --git a/components/bsa/tes4bsa_file.hpp b/components/bsa/tes4bsa_file.hpp new file mode 100644 index 000000000..53a08ffc5 --- /dev/null +++ b/components/bsa/tes4bsa_file.hpp @@ -0,0 +1,108 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (tes4bsa_file.h) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + TES4 stuff added by cc9cii 2018 + + */ + +#ifndef BSA_TES4BSA_FILE_H +#define BSA_TES4BSA_FILE_H + +#include +#include +#include +#include +#include + +namespace Bsa +{ + enum BsaVersion + { + BSAVER_UNKNOWN = 0x0, + BSAVER_TES3 = 0x100, + BSAVER_TES4PLUS = 0x415342 //B, S, A + }; + +/** + This class is used to read "Bethesda Archive Files", or BSAs + in newer formats first introduced in TES IV Oblivion + */ +class TES4BSAFile : public BSAFile +{ +public: + //checks version of BSA from file header + static BsaVersion detectVersion(std::string filePath); + +private: + //special marker for invalid records, + //equal to max uint32_t value + static const uint32_t sInvalidOffset; + + //bit marking compression on file size + static const uint32_t sCompressedFlag; + + struct FileRecord + { + //size of file, with 30th bit containing compression flag + std::uint32_t size; + std::uint32_t offset; + + FileRecord(); + bool isCompressed(bool bsaCompressedByDefault) const; + bool isValid() const; + std::uint32_t getSizeWithoutCompressionFlag() const; + }; + + //if files in BSA without 30th bit enabled are compressed + bool mCompressedByDefault; + + //if file names are not present in file header, + //and we need to look for them at given file offset + bool mEmbeddedFileNames; + + struct FolderRecord + { + std::uint32_t count; + std::uint32_t offset; + std::map files; + }; + std::map mFolders; + + FileRecord getFileRecord(const std::string& filePath) const; + + /// Read header information from the input source + virtual void readHeader(); +public: + TES4BSAFile(); + virtual ~TES4BSAFile(); + + virtual Files::IStreamPtr getFile(const char* file); + virtual Files::IStreamPtr getFile(const FileStruct* file); + +private: + Files::IStreamPtr getFile(const FileRecord& file); + //mFiles used by OpenMW expects uncompressed sizes + void convertCompressedSizesToUncompressed(); + void getBZString(std::string& str, std::istream& filestream); +}; +} + +#endif diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index a527a6ad9..5b9aad437 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -1,4 +1,5 @@ #include "bsaarchive.hpp" +#include namespace VFS { @@ -6,15 +7,30 @@ namespace VFS BsaArchive::BsaArchive(const std::string &filename) { - mFile.open(filename); + mFile = new Bsa::BSAFile(); - const Bsa::BSAFile::FileList &filelist = mFile.getList(); + Bsa::BsaVersion bsaVersion = Bsa::TES4BSAFile::detectVersion(filename); + + if (bsaVersion == Bsa::BSAVER_TES4PLUS) { + mFile = new Bsa::TES4BSAFile(); + } + else { + mFile = new Bsa::BSAFile(); + } + + mFile->open(filename); + + const Bsa::BSAFile::FileList &filelist = mFile->getList(); for(Bsa::BSAFile::FileList::const_iterator it = filelist.begin();it != filelist.end();++it) { - mResources.push_back(BsaArchiveFile(&*it, &mFile)); + mResources.push_back(BsaArchiveFile(&*it, mFile)); } } +BsaArchive::~BsaArchive() { + delete mFile; +} + void BsaArchive::listResources(std::map &out, char (*normalize_function)(char)) { for (std::vector::iterator it = mResources.begin(); it != mResources.end(); ++it) diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index a6e274037..0af965013 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -7,7 +7,6 @@ namespace VFS { - class BsaArchiveFile : public File { public: @@ -23,15 +22,13 @@ namespace VFS { public: BsaArchive(const std::string& filename); - + virtual ~BsaArchive(); virtual void listResources(std::map& out, char (*normalize_function) (char)); private: - Bsa::BSAFile mFile; - + Bsa::BSAFile* mFile; std::vector mResources; }; - } #endif diff --git a/extern/bsaopthash/CMakeLists.txt b/extern/bsaopthash/CMakeLists.txt new file mode 100644 index 000000000..a7f1dcf74 --- /dev/null +++ b/extern/bsaopthash/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.8) + +# This is NOT intended as a stand-alone build system! Instead, you should include this from the main CMakeLists of your project. + +set(BSAOPTHASH_LIBRARY "bsaopthash") + +# Sources +set(SOURCE_FILES + hash.cpp +) + +add_library(${BSAOPTHASH_LIBRARY} STATIC ${SOURCE_FILES}) + +set(BSAOPTHASH_LIBRARIES ${BSAOPTHASH_LIBRARY}) + +link_directories(${CMAKE_CURRENT_BINARY_DIR}) +set(BSAOPTHASH_LIBRARIES ${BSAOPTHASH_LIBRARIES} PARENT_SCOPE) diff --git a/extern/bsaopthash/hash.cpp b/extern/bsaopthash/hash.cpp new file mode 100644 index 000000000..029c54279 --- /dev/null +++ b/extern/bsaopthash/hash.cpp @@ -0,0 +1,108 @@ +/* Version: MPL 1.1/LGPL 3.0 + * + * "The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is BSAopt. + * + * The Initial Developer of the Original Code is + * Ethatron . Portions created by The Initial + * Developer are Copyright (C) 2011 The Initial Developer. + * All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU Library General Public License Version 3 license (the + * "LGPL License"), in which case the provisions of LGPL License are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of the LGPL License and not + * to allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL License. + * If you do not delete the provisions above, a recipient may use your + * version of this file under either the MPL or the LGPL License." + */ + +#include +#include + +#include "hash.hpp" + +std::uint32_t GenOBHashStr(const std::string& s) { + std::uint32_t hash = 0; + + for (std::size_t i = 0; i < s.length(); i++) { + hash *= 0x1003F; + hash += (unsigned char)s[i]; + } + + return hash; +} + +std::uint64_t GenOBHashPair(const std::string& fle, const std::string& ext) { + std::uint64_t hash = 0; + + if (fle.length() > 0) { + hash = (std::uint64_t)( + (((unsigned char)fle[fle.length() - 1]) * 0x1) + + ((fle.length() > 2 ? (unsigned char)fle[fle.length() - 2] : (unsigned char)0) * 0x100) + + (fle.length() * 0x10000) + + (((unsigned char)fle[0]) * 0x1000000) + ); + + if (fle.length() > 3) { + hash += (std::uint64_t)(GenOBHashStr(fle.substr(1, fle.length() - 3)) * 0x100000000); + } + } + + if (ext.length() > 0) { + hash += (std::uint64_t)(GenOBHashStr(ext) * 0x100000000LL); + + unsigned char i = 0; + if (ext == ".nif") i = 1; + if (ext == ".kf" ) i = 2; + if (ext == ".dds") i = 3; + if (ext == ".wav") i = 4; + + if (i != 0) { + unsigned char a = (unsigned char)(((i & 0xfc ) << 5) + (unsigned char)((hash & 0xff000000) >> 24)); + unsigned char b = (unsigned char)(((i & 0xfe ) << 6) + (unsigned char)( hash & 0x000000ff) ); + unsigned char c = (unsigned char)(( i << 7) + (unsigned char)((hash & 0x0000ff00) >> 8)); + + hash -= hash & 0xFF00FFFF; + hash += (std::uint32_t)((a << 24) + b + (c << 8)); + } + } + + return hash; +} + +std::uint64_t GenOBHash(const std::string& path, std::string& file) { + std::transform(file.begin(), file.end(), file.begin(), ::tolower); + std::replace(file.begin(), file.end(), '/', '\\'); + + std::string fle; + std::string ext; + + const char *_fle = file.data(); + const char *_ext = strrchr(_fle, '.'); + if (_ext) { + ext = file.substr((0 + _ext) - _fle); + fle = file.substr(0, ( _ext) - _fle); + } + else { + ext = ""; + fle = file; + } + + if (path.length() && fle.length()) + return GenOBHashPair(path + "\\" + fle, ext); + else + return GenOBHashPair(path + fle, ext); +} diff --git a/extern/bsaopthash/hash.hpp b/extern/bsaopthash/hash.hpp new file mode 100644 index 000000000..cd936530a --- /dev/null +++ b/extern/bsaopthash/hash.hpp @@ -0,0 +1,42 @@ +/* Version: MPL 1.1/LGPL 3.0 + * + * "The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is BSAopt. + * + * The Initial Developer of the Original Code is + * Ethatron . Portions created by The Initial + * Developer are Copyright (C) 2011 The Initial Developer. + * All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU Library General Public License Version 3 license (the + * "LGPL License"), in which case the provisions of LGPL License are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of the LGPL License and not + * to allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL License. + * If you do not delete the provisions above, a recipient may use your + * version of this file under either the MPL or the LGPL License." + */ +#ifndef BSAOPT_HASH_H +#define BSAOPT_HASH_H + +#include + +std::uint32_t GenOBHashStr(const std::string& s); + +std::uint64_t GenOBHashPair(const std::string& fle, const std::string& ext); + +std::uint64_t GenOBHash(const std::string& path, std::string& file); + +#endif // BSAOPT_HASH_H From 4e27fdefdbf157836af8811c328c5feed4d9deda Mon Sep 17 00:00:00 2001 From: Azdul Date: Wed, 16 Jan 2019 20:30:11 +0100 Subject: [PATCH 411/441] Missing boost Linux library --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1ac524ce6..1e0042a93 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,7 +13,7 @@ Debian: before_script: - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - apt-get update -yq - - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev + - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev # - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb From d73165a80c84895d3941b23cd836da3949206cff Mon Sep 17 00:00:00 2001 From: Azdul Date: Wed, 16 Jan 2019 20:37:00 +0100 Subject: [PATCH 412/441] Missing boost library --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1e0042a93..92815fe82 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,7 +13,7 @@ Debian: before_script: - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - apt-get update -yq - - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev + - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libboost-zlib-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev # - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb From b30d2bab570820944bf30d486d07ef690ce1cc9f Mon Sep 17 00:00:00 2001 From: Azdul Date: Wed, 16 Jan 2019 20:47:59 +0100 Subject: [PATCH 413/441] Zlib only for Win32 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c54376c8..0b828348b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -296,9 +296,9 @@ if(QT_STATIC) endif() -set(BOOST_COMPONENTS system filesystem program_options iostreams zlib) +set(BOOST_COMPONENTS system filesystem program_options iostreams) if(WIN32) - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) + set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale zlib) endif(WIN32) IF(BOOST_STATIC) From 33470cb5a2e95710ebe5740bb736406947327688 Mon Sep 17 00:00:00 2001 From: Azdul Date: Wed, 16 Jan 2019 20:53:02 +0100 Subject: [PATCH 414/441] Win32 specific library --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 92815fe82..1e0042a93 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,7 +13,7 @@ Debian: before_script: - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - apt-get update -yq - - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libboost-zlib-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev + - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev # - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb From 96d19e07ca5af0b41837eee7d55bbb353c2aaf7d Mon Sep 17 00:00:00 2001 From: Azdul Date: Wed, 16 Jan 2019 20:59:07 +0100 Subject: [PATCH 415/441] Fix Linux build --- components/CMakeLists.txt | 6 +++--- extern/bsaopthash/hash.cpp | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7ab08292e..bc99550d0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -218,8 +218,7 @@ target_link_libraries(components ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_IOSTREAMS_LIBRARY} - ${Boost_ZLIB_LIBRARY} + ${Boost_IOSTREAMS_LIBRARY} ${OSG_LIBRARIES} ${OPENTHREADS_LIBRARIES} ${OSGPARTICLE_LIBRARIES} @@ -241,7 +240,8 @@ target_link_libraries(components if (WIN32) target_link_libraries(components - ${Boost_LOCALE_LIBRARY}) + ${Boost_LOCALE_LIBRARY} + ${Boost_ZLIB_LIBRARY}) endif() if (USE_QT) diff --git a/extern/bsaopthash/hash.cpp b/extern/bsaopthash/hash.cpp index 029c54279..08c807da4 100644 --- a/extern/bsaopthash/hash.cpp +++ b/extern/bsaopthash/hash.cpp @@ -31,6 +31,7 @@ #include #include +#include #include "hash.hpp" From 9cce6ecfe3ea938289349fa4beb614ba032e88bc Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Mon, 14 Jan 2019 19:47:08 +0100 Subject: [PATCH 416/441] [macOS] Update deploy script to use ssh/scp --- CI/deploy.osx.sh | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/CI/deploy.osx.sh b/CI/deploy.osx.sh index 5fa2b70a3..339ff955d 100755 --- a/CI/deploy.osx.sh +++ b/CI/deploy.osx.sh @@ -1,11 +1,26 @@ #!/bin/sh -cd build +# This script expect the following environment variables to be set: +# - OSX_DEPLOY_KEY: private SSH key, must be encoded like this before adding it to Travis secrets: https://github.com/travis-ci/travis-ci/issues/7715#issuecomment-433301692 +# - OSX_DEPLOY_HOST: string specifying SSH of the following format: ssh-user@ssh-host +# - OSX_DEPLOY_PORT: SSH port, it can't be a part of the host string because scp doesn't accept hosts with ports +# - OSX_DEPLOY_HOST_FINGERPRINT: fingerprint of the host, can be obtained by using ssh-keygen -F [host]:port & putting it in double quotes when adding to Travis secrets -DATE=`date +'%d%m%Y'` -SHORT_COMMIT=`git rev-parse --short ${TRAVIS_COMMIT}` +SSH_KEY_PATH="$HOME/.ssh/openmw_deploy" +REMOTE_PATH="\$HOME/nightly" + +echo "$OSX_DEPLOY_KEY" > "$SSH_KEY_PATH" +chmod 600 "$SSH_KEY_PATH" +echo "$OSX_DEPLOY_HOST_FINGERPRINT" >> "$HOME/.ssh/known_hosts" + +cd build || exit 1 + +DATE=$(date +'%d%m%Y') +SHORT_COMMIT=$(git rev-parse --short "${TRAVIS_COMMIT}") TARGET_FILENAME="OpenMW-${DATE}-${SHORT_COMMIT}.dmg" -if ! curl --ssl -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}" --silent | grep $SHORT_COMMIT > /dev/null; then - curl --ssl --ftp-create-dirs -T *.dmg -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}${TARGET_FILENAME}" +if ! ssh -p "$OSX_DEPLOY_PORT" -i "$SSH_KEY_PATH" "$OSX_DEPLOY_HOST" sh -c "ls \"$REMOTE_PATH\"" | grep "$SHORT_COMMIT" > /dev/null; then + scp -P "$OSX_DEPLOY_PORT" -i "$SSH_KEY_PATH" ./*.dmg "$OSX_DEPLOY_HOST:$REMOTE_PATH/$TARGET_FILENAME" +else + echo "An existing nightly build for commit ${SHORT_COMMIT} has been found, skipping upload." fi From c6fd979c421c076d7cf304c116810edfde5c02d1 Mon Sep 17 00:00:00 2001 From: Azdul Date: Thu, 17 Jan 2019 05:42:26 +0100 Subject: [PATCH 417/441] Spaces instead of tabs in CMakeLists.txt --- components/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index bc99550d0..a289f6647 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -232,7 +232,7 @@ target_link_libraries(components ${SDL2_LIBRARIES} ${OPENGL_gl_LIBRARY} ${MyGUI_LIBRARIES} - ${BSAOPTHASH_LIBRARIES} + ${BSAOPTHASH_LIBRARIES} RecastNavigation::DebugUtils RecastNavigation::Detour RecastNavigation::Recast From b9a751427823d3cb67dcc8560348ed693e421c3f Mon Sep 17 00:00:00 2001 From: Azdul Date: Thu, 17 Jan 2019 07:08:25 +0100 Subject: [PATCH 418/441] Memory leak fixed. Proper file names in headers. --- components/bsa/memorystream.hpp | 2 +- components/bsa/tes4bsa_file.hpp | 2 +- components/vfs/bsaarchive.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/bsa/memorystream.hpp b/components/bsa/memorystream.hpp index aed39e6d6..0182e901a 100644 --- a/components/bsa/memorystream.hpp +++ b/components/bsa/memorystream.hpp @@ -4,7 +4,7 @@ Email: < korslund@gmail.com > WWW: http://openmw.sourceforge.net/ - This file (memorystream.h) is part of the OpenMW package. + This file (memorystream.hpp) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License diff --git a/components/bsa/tes4bsa_file.hpp b/components/bsa/tes4bsa_file.hpp index 53a08ffc5..b2c125b62 100644 --- a/components/bsa/tes4bsa_file.hpp +++ b/components/bsa/tes4bsa_file.hpp @@ -4,7 +4,7 @@ Email: < korslund@gmail.com > WWW: http://openmw.sourceforge.net/ - This file (tes4bsa_file.h) is part of the OpenMW package. + This file (tes4bsa_file.hpp) is part of the OpenMW package. OpenMW is distributed as free software: you can redistribute it and/or modify it under the terms of the GNU General Public License diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index 5b9aad437..b55082386 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -7,7 +7,7 @@ namespace VFS BsaArchive::BsaArchive(const std::string &filename) { - mFile = new Bsa::BSAFile(); + mFile = nullptr; Bsa::BsaVersion bsaVersion = Bsa::TES4BSAFile::detectVersion(filename); From e5028192465db9c6ecb0242d45e53003fde9c75b Mon Sep 17 00:00:00 2001 From: Azdul Date: Thu, 17 Jan 2019 08:08:03 +0100 Subject: [PATCH 419/441] Proper conversion from shared_ptr to shared_ptr --- components/bsa/tes4bsa_file.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/components/bsa/tes4bsa_file.cpp b/components/bsa/tes4bsa_file.cpp index 5c36061d5..2b04f2f1c 100644 --- a/components/bsa/tes4bsa_file.cpp +++ b/components/bsa/tes4bsa_file.cpp @@ -353,13 +353,12 @@ Files::IStreamPtr TES4BSAFile::getFile(const FileRecord& fileRecord) { inputStreamBuf.push(boost::iostreams::zlib_decompressor()); inputStreamBuf.push(*fileStream); - std::shared_ptr > bufferVec = std::make_shared>(uncompressedSize); - Bsa::MemoryInputStream* result = new MemoryInputStream(uncompressedSize); + std::shared_ptr memoryStreamPtr = std::make_shared(uncompressedSize); - boost::iostreams::basic_array_sink sr(result->getRawData(), uncompressedSize); + boost::iostreams::basic_array_sink sr(memoryStreamPtr->getRawData(), uncompressedSize); boost::iostreams::copy(inputStreamBuf, sr); - std::istream* pPtr = (std::istream*) result; - return std::shared_ptr(pPtr); + + return std::shared_ptr(memoryStreamPtr, (std::istream*)memoryStreamPtr.get()); } return Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.size); From caa0487c6a77aed16a5f41f6eb8a1137a5f4decc Mon Sep 17 00:00:00 2001 From: Azdul Date: Thu, 17 Jan 2019 08:42:43 +0100 Subject: [PATCH 420/441] Replace raw pointer in BsaArchive with unique_ptr --- components/vfs/bsaarchive.cpp | 9 +++------ components/vfs/bsaarchive.hpp | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index b55082386..c775ca6e4 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -7,15 +7,13 @@ namespace VFS BsaArchive::BsaArchive(const std::string &filename) { - mFile = nullptr; - Bsa::BsaVersion bsaVersion = Bsa::TES4BSAFile::detectVersion(filename); if (bsaVersion == Bsa::BSAVER_TES4PLUS) { - mFile = new Bsa::TES4BSAFile(); + mFile = std::make_unique(); } else { - mFile = new Bsa::BSAFile(); + mFile = std::make_unique(); } mFile->open(filename); @@ -23,12 +21,11 @@ BsaArchive::BsaArchive(const std::string &filename) const Bsa::BSAFile::FileList &filelist = mFile->getList(); for(Bsa::BSAFile::FileList::const_iterator it = filelist.begin();it != filelist.end();++it) { - mResources.push_back(BsaArchiveFile(&*it, mFile)); + mResources.push_back(BsaArchiveFile(&*it, mFile.get())); } } BsaArchive::~BsaArchive() { - delete mFile; } void BsaArchive::listResources(std::map &out, char (*normalize_function)(char)) diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 0af965013..a87f278c7 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -26,7 +26,7 @@ namespace VFS virtual void listResources(std::map& out, char (*normalize_function) (char)); private: - Bsa::BSAFile* mFile; + std::unique_ptr mFile; std::vector mResources; }; } From d55cc4d3eaf972060adad684aaf401fd10665510 Mon Sep 17 00:00:00 2001 From: Azdul Date: Thu, 17 Jan 2019 08:52:30 +0100 Subject: [PATCH 421/441] Linux requires header --- components/vfs/bsaarchive.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index c775ca6e4..75121ef50 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -1,5 +1,6 @@ #include "bsaarchive.hpp" #include +#include namespace VFS { From 4c499983caf2417d100e57b9e6653a97dc5eb72c Mon Sep 17 00:00:00 2001 From: Azdul Date: Thu, 17 Jan 2019 08:58:25 +0100 Subject: [PATCH 422/441] Fixed warnings reported by GCC --- components/bsa/tes4bsa_file.cpp | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/components/bsa/tes4bsa_file.cpp b/components/bsa/tes4bsa_file.cpp index 2b04f2f1c..226d675f6 100644 --- a/components/bsa/tes4bsa_file.cpp +++ b/components/bsa/tes4bsa_file.cpp @@ -127,7 +127,7 @@ void TES4BSAFile::readHeader() // Get essential header numbers //size_t dirsize, filenum; - std::uint32_t archiveFlags, folderCount, fileCount, totalFileNameLength; + std::uint32_t archiveFlags, folderCount, totalFileNameLength; { // First 36 bytes std::uint32_t header[9]; @@ -154,7 +154,6 @@ void TES4BSAFile::readHeader() // archiveFlags = header[3]; folderCount = header[4]; - fileCount = header[5]; totalFileNameLength = header[7]; mCompressedByDefault = (archiveFlags & 0x4) != 0; @@ -381,23 +380,21 @@ BsaVersion TES4BSAFile::detectVersion(std::string filePath) return BSAVER_UNKNOWN; } - // Get essential header numbers - size_t dirsize, filenum; - { - // First 12 bytes - uint32_t head[3]; + + // First 12 bytes + uint32_t head[3]; - input.read(reinterpret_cast(head), 12); + input.read(reinterpret_cast(head), 12); - if (head[0] == static_cast(BSAVER_TES3)) { - return BSAVER_TES3; - } + if (head[0] == static_cast(BSAVER_TES3)) { + return BSAVER_TES3; + } - if (head[0] = static_cast(BSAVER_TES4PLUS)) { - return BSAVER_TES4PLUS; - } + if (head[0] == static_cast(BSAVER_TES4PLUS)) { + return BSAVER_TES4PLUS; } + return BSAVER_UNKNOWN; } From 0c3a3ea241c8d211059f387810d413382feeccd5 Mon Sep 17 00:00:00 2001 From: Azdul Date: Thu, 17 Jan 2019 09:11:19 +0100 Subject: [PATCH 423/441] Strange issue with Linux compilation ( header) --- components/vfs/bsaarchive.cpp | 2 -- components/vfs/bsaarchive.hpp | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index 75121ef50..76579a6f6 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -1,11 +1,9 @@ #include "bsaarchive.hpp" #include -#include namespace VFS { - BsaArchive::BsaArchive(const std::string &filename) { Bsa::BsaVersion bsaVersion = Bsa::TES4BSAFile::detectVersion(filename); diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index a87f278c7..5a859c72f 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -4,6 +4,7 @@ #include "archive.hpp" #include +#include namespace VFS { From 777a50cb64153dbf1657a91607f51d765a6ef7e1 Mon Sep 17 00:00:00 2001 From: "Adam.Fandrejewski" Date: Thu, 17 Jan 2019 09:56:11 +0100 Subject: [PATCH 424/441] Replaced C++ 14 make_unique with C++ 11 compliant code --- components/vfs/bsaarchive.cpp | 5 +++-- components/vfs/bsaarchive.hpp | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index 76579a6f6..268a53047 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -1,5 +1,6 @@ #include "bsaarchive.hpp" #include +#include namespace VFS { @@ -9,10 +10,10 @@ BsaArchive::BsaArchive(const std::string &filename) Bsa::BsaVersion bsaVersion = Bsa::TES4BSAFile::detectVersion(filename); if (bsaVersion == Bsa::BSAVER_TES4PLUS) { - mFile = std::make_unique(); + mFile = std::unique_ptr(new Bsa::TES4BSAFile()); } else { - mFile = std::make_unique(); + mFile = std::unique_ptr(new Bsa::BSAFile()); } mFile->open(filename); diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 5a859c72f..a87f278c7 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -4,7 +4,6 @@ #include "archive.hpp" #include -#include namespace VFS { From a78d98df273e266c05c73837a5961fbf6a875ccf Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Thu, 17 Jan 2019 10:38:34 +0000 Subject: [PATCH 425/441] Fix crashcatcher --- components/crashcatcher/crashcatcher.cpp | 89 +++++++++++++++++------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 2e2ddd1f2..006b190ba 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -17,6 +17,7 @@ #include #include +#include namespace bfs = boost::filesystem; @@ -34,9 +35,13 @@ namespace bfs = boost::filesystem; #if defined(__APPLE__) #include +#include #endif -#define UNUSED(x) (void)(x) +#if defined(__FreeBSD__) +#include +#include +#endif static const char crash_switch[] = "--cc-handle-crash"; @@ -413,6 +418,39 @@ static void crash_handler(const char *logfile) exit(0); } +static void getExecPath(char **argv) +{ +#if defined (__FreeBSD__) + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + size_t size = sizeof(argv0); + + if (sysctl(mib, 4, argv0, &size, nullptr, 0) == 0) + return; +#endif + +#if defined (__APPLE__) + if(proc_pidpath(getpid(), argv0, sizeof(argv0)) > 0) + return; +#endif + int cwdlen; + const char *statusPaths[] = {"/proc/self/exe", "/proc/self/file", "/proc/curproc/exe", "/proc/curproc/file"}; + memset(argv0, 0, sizeof(argv0)); + + for(const char *path : statusPaths) + { + if (readlink(path, argv0, sizeof(argv0)) != -1) + return; + } + + if(argv[0][0] == '/') + snprintf(argv0, sizeof(argv0), "%s", argv[0]); + else if (getcwd(argv0, sizeof(argv0)) != NULL) + { + cwdlen = strlen(argv0); + snprintf(argv0+cwdlen, sizeof(argv0)-cwdlen, "/%s", argv[0]); + } +} + int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*)) { struct sigaction sa; @@ -424,20 +462,7 @@ int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *sig cc_user_info = user_info; - if(argv[0][0] == '/') - snprintf(argv0, sizeof(argv0), "%s", argv[0]); - else - { - { - /* we don't want to disable "ignoring return value" warnings, so we make - * a special exception here. */ - char * unused; - unused = getcwd(argv0, sizeof(argv0)); - UNUSED(unused); - } - retval = strlen(argv0); - snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]); - } + getExecPath(argv); /* Set an alternate signal stack so SIGSEGVs caused by stack overflows * still run */ @@ -467,20 +492,24 @@ int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *sig static bool is_debugger_present() { -#if !defined (__APPLE__) - bfs::ifstream file((bfs::path("/proc/self/status"))); - while (!file.eof()) +#if defined (__linux__) + bfs::path procstatus = bfs::path("/proc/self/status"); + if (bfs::exists(procstatus)) { - std::string word; - file >> word; - if (word == "TracerPid:") + bfs::ifstream file((procstatus)); + while (!file.eof()) { + std::string word; file >> word; - return word != "0"; + if (word == "TracerPid:") + { + file >> word; + return word != "0"; + } } } return false; -#else +#elif defined(__APPLE__) int junk; int mib[4]; struct kinfo_proc info; @@ -508,12 +537,24 @@ static bool is_debugger_present() // We're being debugged if the P_TRACED flag is set. return (info.kp_proc.p_flag & P_TRACED) != 0; +#elif defined(__FreeBSD__) + struct kinfo_proc info; + size_t size = sizeof(info); + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; + + if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) == 0) + return (info.ki_flag & P_TRACED) != 0; + else + perror("Failed to retrieve process info"); + return false; +#else + return false; #endif } void crashCatcherInstall(int argc, char **argv, const std::string &crashLogPath) { - if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_present()) + if ((argc == 2 && strcmp(argv[1], crash_switch) == 0) || !is_debugger_present()) { int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str(), nullptr) == -1) From 42ea609d1db8b1bdaaf3b24de6215511ab635a88 Mon Sep 17 00:00:00 2001 From: Azdul Date: Fri, 18 Jan 2019 09:03:34 +0100 Subject: [PATCH 426/441] Remove files with incorrect Git history / attribution --- components/bsa/tes4bsa_file.cpp | 430 ------------------------------- components/bsa/tes4bsa_file.hpp | 108 -------- extern/bsaopthash/CMakeLists.txt | 17 -- extern/bsaopthash/hash.cpp | 109 -------- extern/bsaopthash/hash.hpp | 42 --- 5 files changed, 706 deletions(-) delete mode 100644 components/bsa/tes4bsa_file.cpp delete mode 100644 components/bsa/tes4bsa_file.hpp delete mode 100644 extern/bsaopthash/CMakeLists.txt delete mode 100644 extern/bsaopthash/hash.cpp delete mode 100644 extern/bsaopthash/hash.hpp diff --git a/components/bsa/tes4bsa_file.cpp b/components/bsa/tes4bsa_file.cpp deleted file mode 100644 index 226d675f6..000000000 --- a/components/bsa/tes4bsa_file.cpp +++ /dev/null @@ -1,430 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (tes4bsa_file.cpp) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - TES4 stuff added by cc9cii 2018 - - */ -#include "tes4bsa_file.hpp" - -#include -#include -#include -#include -#include -#include - -#include // see: http://en.uesp.net/wiki/Tes4Mod:Hash_Calculation -#include -#include -#include -#include -#include -#include -#include - -namespace Bsa -{ - -//special marker for invalid records, -//equal to max uint32_t value -const uint32_t TES4BSAFile::sInvalidOffset = std::numeric_limits::max(); - -//bit marking compression on file size -const uint32_t TES4BSAFile::sCompressedFlag = 1u << 30u; - - -TES4BSAFile::FileRecord::FileRecord() : size(0), offset(sInvalidOffset) -{ } - -bool TES4BSAFile::FileRecord::isValid() const -{ - return offset != sInvalidOffset; -} - -bool TES4BSAFile::FileRecord::isCompressed(bool bsaCompressedByDefault) const -{ - bool compressionFlagEnabled = ((size & sCompressedFlag) == sCompressedFlag); - - if (bsaCompressedByDefault) { - return !compressionFlagEnabled; - } - return compressionFlagEnabled; -} - -std::uint32_t TES4BSAFile::FileRecord::getSizeWithoutCompressionFlag() const { - return size & (~sCompressedFlag); -} - -TES4BSAFile::TES4BSAFile() - : mCompressedByDefault(false), mEmbeddedFileNames(false) -{ } - -TES4BSAFile::~TES4BSAFile() -{ } - -void TES4BSAFile::getBZString(std::string& str, std::istream& filestream) -{ - char size = 0; - filestream.read(&size, 1); - - boost::scoped_array buf(new char[size]); - filestream.read(buf.get(), size); - - if (buf[size - 1] != 0) - { - str.assign(buf.get(), size); - if (str.size() != ((size_t)size)) { - fail("getBZString string size mismatch"); - } - } - else - { - str.assign(buf.get(), size - 1); // don't copy null terminator - if (str.size() != ((size_t)size - 1)) { - fail("getBZString string size mismatch (null terminator)"); - } - } - - return; -} - -/// Read header information from the input source -void TES4BSAFile::readHeader() -{ - assert(!mIsLoaded); - - namespace bfs = boost::filesystem; - bfs::ifstream input(bfs::path(mFilename), std::ios_base::binary); - - // Total archive size - std::streamoff fsize = 0; - if (input.seekg(0, std::ios_base::end)) - { - fsize = input.tellg(); - input.seekg(0); - } - - if (fsize < 36) // header is 36 bytes - fail("File too small to be a valid BSA archive"); - - // Get essential header numbers - //size_t dirsize, filenum; - std::uint32_t archiveFlags, folderCount, totalFileNameLength; - { - // First 36 bytes - std::uint32_t header[9]; - - input.read(reinterpret_cast(header), 36); - - if (header[0] != 0x00415342 /*"BSA\x00"*/ || (header[1] != 0x67 /*TES4*/ && header[1] != 0x68 /*TES5*/)) - fail("Unrecognized TES4 BSA header"); - - // header[2] is offset, should be 36 = 0x24 which is the size of the header - - // Oblivion - Meshes.bsa - // - // 0111 1000 0111 = 0x0787 - // ^^^ ^ ^^^ - // ||| | ||+-- has names for dirs (mandatory?) - // ||| | |+--- has names for files (mandatory?) - // ||| | +---- files are compressed by default - // ||| | - // ||| +---------- unknown (TES5: retain strings during startup) - // ||+------------ unknown (TES5: embedded file names) - // |+------------- unknown - // +-------------- unknown - // - archiveFlags = header[3]; - folderCount = header[4]; - totalFileNameLength = header[7]; - - mCompressedByDefault = (archiveFlags & 0x4) != 0; - mEmbeddedFileNames = header[1] == 0x68 /*TES5*/ && (archiveFlags & 0x100) != 0; - } - - // folder records - std::uint64_t hash; - FolderRecord fr; - for (std::uint32_t i = 0; i < folderCount; ++i) - { - input.read(reinterpret_cast(&hash), 8); - input.read(reinterpret_cast(&fr.count), 4); - input.read(reinterpret_cast(&fr.offset), 4); - - std::map::const_iterator lb = mFolders.lower_bound(hash); - if (lb != mFolders.end() && !(mFolders.key_comp()(hash, lb->first))) - fail("Archive found duplicate folder name hash"); - else - mFolders.insert(lb, std::pair(hash, fr)); - } - - // file record blocks - std::uint64_t fileHash; - FileRecord file; - - std::string folder(""); - std::uint64_t folderHash; - if ((archiveFlags & 0x1) == 0) - { - folderCount = 1; - } - - mFiles.clear(); - std::vector fullPaths; - - for (std::uint32_t i = 0; i < folderCount; ++i) - { - if ((archiveFlags & 0x1) != 0) - getBZString(folder, input); - - std::string emptyString; - folderHash = GenOBHash(folder, emptyString); - - std::map::iterator iter = mFolders.find(folderHash); - if (iter == mFolders.end()) - fail("Archive folder name hash not found"); - - for (std::uint32_t j = 0; j < iter->second.count; ++j) - { - input.read(reinterpret_cast(&fileHash), 8); - input.read(reinterpret_cast(&file.size), 4); - input.read(reinterpret_cast(&file.offset), 4); - - std::map::const_iterator lb = iter->second.files.lower_bound(fileHash); - if (lb != iter->second.files.end() && !(iter->second.files.key_comp()(fileHash, lb->first))) - { - fail("Archive found duplicate file name hash"); - } - - iter->second.files.insert(lb, std::pair(fileHash, file)); - - FileStruct fileStruct; - fileStruct.fileSize = file.getSizeWithoutCompressionFlag(); - fileStruct.offset = file.offset; - fileStruct.name = nullptr; - mFiles.push_back(fileStruct); - - fullPaths.push_back(folder); - } - } - - // file record blocks - if ((archiveFlags & 0x2) != 0) - { - mStringBuf.resize(totalFileNameLength); - input.read(&mStringBuf[0], mStringBuf.size()); - } - - size_t mStringBuffOffset = 0; - size_t totalStringsSize = 0; - for (std::uint32_t fileIndex = 0; fileIndex < mFiles.size(); ++fileIndex) { - - if (mStringBuffOffset >= totalFileNameLength) { - fail("Corrupted names record in BSA file"); - } - - //The vector guarantees that its elements occupy contiguous memory - mFiles[fileIndex].name = reinterpret_cast(mStringBuf.data() + mStringBuffOffset); - - fullPaths.at(fileIndex) += "\\" + std::string(mStringBuf.data() + mStringBuffOffset); - - while (mStringBuffOffset < totalFileNameLength) { - if (mStringBuf[mStringBuffOffset] != '\0') { - mStringBuffOffset++; - } - else { - mStringBuffOffset++; - break; - } - } - //we want to keep one more 0 character at the end of each string - totalStringsSize += fullPaths.at(fileIndex).length() + 1u; - } - mStringBuf.resize(totalStringsSize); - - mStringBuffOffset = 0; - for (std::uint32_t fileIndex = 0u; fileIndex < mFiles.size(); fileIndex++) { - size_t stringLength = fullPaths.at(fileIndex).length(); - - std::copy(fullPaths.at(fileIndex).c_str(), - //plus 1 because we also want to copy 0 at the end of the string - fullPaths.at(fileIndex).c_str() + stringLength + 1u, - mStringBuf.data() + mStringBuffOffset); - - mFiles[fileIndex].name = reinterpret_cast(mStringBuf.data() + mStringBuffOffset); - - mLookup[reinterpret_cast(mStringBuf.data() + mStringBuffOffset)] = fileIndex; - mStringBuffOffset += stringLength + 1u; - } - - if (mStringBuffOffset != mStringBuf.size()) { - fail("Could not resolve names of files in BSA file"); - } - - convertCompressedSizesToUncompressed(); - mIsLoaded = true; -} - -TES4BSAFile::FileRecord TES4BSAFile::getFileRecord(const std::string& filePath) const -{ - boost::filesystem::path p(filePath); - std::string stem = p.stem().string(); - std::string ext = p.extension().string(); - std::string filename = p.filename().string(); - p.remove_filename(); - - std::string folder = p.string(); - // GenOBHash already converts to lowercase and replaces file separators but not for path - boost::algorithm::to_lower(folder); - std::replace(folder.begin(), folder.end(), '/', '\\'); - - std::string emptyString; - std::uint64_t folderHash = GenOBHash(folder, emptyString); - - std::map::const_iterator it = mFolders.find(folderHash); - if (it == mFolders.end()) - { - return FileRecord(); // folder not found, return default which has offset of -1 - } - - boost::algorithm::to_lower(stem); - boost::algorithm::to_lower(ext); - std::uint64_t fileHash = GenOBHashPair(stem, ext); - std::map::const_iterator fileInDirIter = it->second.files.find(fileHash); - if (fileInDirIter == it->second.files.end()) - { - return FileRecord(); // file not found, return default which has offset of -1 - } - - return fileInDirIter->second; -} - -Files::IStreamPtr TES4BSAFile::getFile(const FileStruct* file) { - - FileRecord fileRec = getFileRecord(file->name); - return getFile(fileRec); -} - -Files::IStreamPtr TES4BSAFile::getFile(const char* file) -{ - FileRecord fileRec = getFileRecord(file); - if (!fileRec.isValid()) { - fail("File not found: " + std::string(file)); - } - - return getFile(fileRec); -} - -Files::IStreamPtr TES4BSAFile::getFile(const FileRecord& fileRecord) { - - if (fileRecord.isCompressed(mCompressedByDefault)) { - Files::IStreamPtr streamPtr = Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.getSizeWithoutCompressionFlag()); - - std::istream* fileStream = streamPtr.get(); - - if (mEmbeddedFileNames) { - std::string embeddedFileName; - getBZString(embeddedFileName,*fileStream); - } - - uint32_t uncompressedSize = 0u; - fileStream->read(reinterpret_cast(&uncompressedSize), sizeof(uncompressedSize)); - - boost::iostreams::filtering_streambuf inputStreamBuf; - inputStreamBuf.push(boost::iostreams::zlib_decompressor()); - inputStreamBuf.push(*fileStream); - - std::shared_ptr memoryStreamPtr = std::make_shared(uncompressedSize); - - boost::iostreams::basic_array_sink sr(memoryStreamPtr->getRawData(), uncompressedSize); - boost::iostreams::copy(inputStreamBuf, sr); - - return std::shared_ptr(memoryStreamPtr, (std::istream*)memoryStreamPtr.get()); - } - - return Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.size); -} - -BsaVersion TES4BSAFile::detectVersion(std::string filePath) -{ - namespace bfs = boost::filesystem; - bfs::ifstream input(bfs::path(filePath), std::ios_base::binary); - - // Total archive size - std::streamoff fsize = 0; - if (input.seekg(0, std::ios_base::end)) - { - fsize = input.tellg(); - input.seekg(0); - } - - if (fsize < 12) { - return BSAVER_UNKNOWN; - } - - // Get essential header numbers - - // First 12 bytes - uint32_t head[3]; - - input.read(reinterpret_cast(head), 12); - - if (head[0] == static_cast(BSAVER_TES3)) { - return BSAVER_TES3; - } - - if (head[0] == static_cast(BSAVER_TES4PLUS)) { - return BSAVER_TES4PLUS; - } - - return BSAVER_UNKNOWN; -} - -//mFiles used by OpenMW expects uncompressed sizes -void TES4BSAFile::convertCompressedSizesToUncompressed() -{ - for (auto iter = mFiles.begin(); iter != mFiles.end(); ++iter) - { - const FileRecord& fileRecord = getFileRecord(iter->name); - if (!fileRecord.isValid()) - { - fail("Could not find file " + std::string(iter->name) + " in BSA"); - } - - if (!fileRecord.isCompressed(mCompressedByDefault)) - { - //no need to fix fileSize in mFiles - uncompressed size already set - continue; - } - - Files::IStreamPtr dataBegin = Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.getSizeWithoutCompressionFlag()); - - if (mEmbeddedFileNames) - { - std::string embeddedFileName; - getBZString(embeddedFileName, *(dataBegin.get())); - } - - dataBegin->read(reinterpret_cast(&(iter->fileSize)), sizeof(iter->fileSize)); - } -} - -} //namespace Bsa diff --git a/components/bsa/tes4bsa_file.hpp b/components/bsa/tes4bsa_file.hpp deleted file mode 100644 index b2c125b62..000000000 --- a/components/bsa/tes4bsa_file.hpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (tes4bsa_file.hpp) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - TES4 stuff added by cc9cii 2018 - - */ - -#ifndef BSA_TES4BSA_FILE_H -#define BSA_TES4BSA_FILE_H - -#include -#include -#include -#include -#include - -namespace Bsa -{ - enum BsaVersion - { - BSAVER_UNKNOWN = 0x0, - BSAVER_TES3 = 0x100, - BSAVER_TES4PLUS = 0x415342 //B, S, A - }; - -/** - This class is used to read "Bethesda Archive Files", or BSAs - in newer formats first introduced in TES IV Oblivion - */ -class TES4BSAFile : public BSAFile -{ -public: - //checks version of BSA from file header - static BsaVersion detectVersion(std::string filePath); - -private: - //special marker for invalid records, - //equal to max uint32_t value - static const uint32_t sInvalidOffset; - - //bit marking compression on file size - static const uint32_t sCompressedFlag; - - struct FileRecord - { - //size of file, with 30th bit containing compression flag - std::uint32_t size; - std::uint32_t offset; - - FileRecord(); - bool isCompressed(bool bsaCompressedByDefault) const; - bool isValid() const; - std::uint32_t getSizeWithoutCompressionFlag() const; - }; - - //if files in BSA without 30th bit enabled are compressed - bool mCompressedByDefault; - - //if file names are not present in file header, - //and we need to look for them at given file offset - bool mEmbeddedFileNames; - - struct FolderRecord - { - std::uint32_t count; - std::uint32_t offset; - std::map files; - }; - std::map mFolders; - - FileRecord getFileRecord(const std::string& filePath) const; - - /// Read header information from the input source - virtual void readHeader(); -public: - TES4BSAFile(); - virtual ~TES4BSAFile(); - - virtual Files::IStreamPtr getFile(const char* file); - virtual Files::IStreamPtr getFile(const FileStruct* file); - -private: - Files::IStreamPtr getFile(const FileRecord& file); - //mFiles used by OpenMW expects uncompressed sizes - void convertCompressedSizesToUncompressed(); - void getBZString(std::string& str, std::istream& filestream); -}; -} - -#endif diff --git a/extern/bsaopthash/CMakeLists.txt b/extern/bsaopthash/CMakeLists.txt deleted file mode 100644 index a7f1dcf74..000000000 --- a/extern/bsaopthash/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -# This is NOT intended as a stand-alone build system! Instead, you should include this from the main CMakeLists of your project. - -set(BSAOPTHASH_LIBRARY "bsaopthash") - -# Sources -set(SOURCE_FILES - hash.cpp -) - -add_library(${BSAOPTHASH_LIBRARY} STATIC ${SOURCE_FILES}) - -set(BSAOPTHASH_LIBRARIES ${BSAOPTHASH_LIBRARY}) - -link_directories(${CMAKE_CURRENT_BINARY_DIR}) -set(BSAOPTHASH_LIBRARIES ${BSAOPTHASH_LIBRARIES} PARENT_SCOPE) diff --git a/extern/bsaopthash/hash.cpp b/extern/bsaopthash/hash.cpp deleted file mode 100644 index 08c807da4..000000000 --- a/extern/bsaopthash/hash.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* Version: MPL 1.1/LGPL 3.0 - * - * "The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" - * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the - * License for the specific language governing rights and limitations - * under the License. - * - * The Original Code is BSAopt. - * - * The Initial Developer of the Original Code is - * Ethatron . Portions created by The Initial - * Developer are Copyright (C) 2011 The Initial Developer. - * All Rights Reserved. - * - * Alternatively, the contents of this file may be used under the terms - * of the GNU Library General Public License Version 3 license (the - * "LGPL License"), in which case the provisions of LGPL License are - * applicable instead of those above. If you wish to allow use of your - * version of this file only under the terms of the LGPL License and not - * to allow others to use your version of this file under the MPL, - * indicate your decision by deleting the provisions above and replace - * them with the notice and other provisions required by the LGPL License. - * If you do not delete the provisions above, a recipient may use your - * version of this file under either the MPL or the LGPL License." - */ - -#include -#include -#include - -#include "hash.hpp" - -std::uint32_t GenOBHashStr(const std::string& s) { - std::uint32_t hash = 0; - - for (std::size_t i = 0; i < s.length(); i++) { - hash *= 0x1003F; - hash += (unsigned char)s[i]; - } - - return hash; -} - -std::uint64_t GenOBHashPair(const std::string& fle, const std::string& ext) { - std::uint64_t hash = 0; - - if (fle.length() > 0) { - hash = (std::uint64_t)( - (((unsigned char)fle[fle.length() - 1]) * 0x1) + - ((fle.length() > 2 ? (unsigned char)fle[fle.length() - 2] : (unsigned char)0) * 0x100) + - (fle.length() * 0x10000) + - (((unsigned char)fle[0]) * 0x1000000) - ); - - if (fle.length() > 3) { - hash += (std::uint64_t)(GenOBHashStr(fle.substr(1, fle.length() - 3)) * 0x100000000); - } - } - - if (ext.length() > 0) { - hash += (std::uint64_t)(GenOBHashStr(ext) * 0x100000000LL); - - unsigned char i = 0; - if (ext == ".nif") i = 1; - if (ext == ".kf" ) i = 2; - if (ext == ".dds") i = 3; - if (ext == ".wav") i = 4; - - if (i != 0) { - unsigned char a = (unsigned char)(((i & 0xfc ) << 5) + (unsigned char)((hash & 0xff000000) >> 24)); - unsigned char b = (unsigned char)(((i & 0xfe ) << 6) + (unsigned char)( hash & 0x000000ff) ); - unsigned char c = (unsigned char)(( i << 7) + (unsigned char)((hash & 0x0000ff00) >> 8)); - - hash -= hash & 0xFF00FFFF; - hash += (std::uint32_t)((a << 24) + b + (c << 8)); - } - } - - return hash; -} - -std::uint64_t GenOBHash(const std::string& path, std::string& file) { - std::transform(file.begin(), file.end(), file.begin(), ::tolower); - std::replace(file.begin(), file.end(), '/', '\\'); - - std::string fle; - std::string ext; - - const char *_fle = file.data(); - const char *_ext = strrchr(_fle, '.'); - if (_ext) { - ext = file.substr((0 + _ext) - _fle); - fle = file.substr(0, ( _ext) - _fle); - } - else { - ext = ""; - fle = file; - } - - if (path.length() && fle.length()) - return GenOBHashPair(path + "\\" + fle, ext); - else - return GenOBHashPair(path + fle, ext); -} diff --git a/extern/bsaopthash/hash.hpp b/extern/bsaopthash/hash.hpp deleted file mode 100644 index cd936530a..000000000 --- a/extern/bsaopthash/hash.hpp +++ /dev/null @@ -1,42 +0,0 @@ -/* Version: MPL 1.1/LGPL 3.0 - * - * "The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" - * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the - * License for the specific language governing rights and limitations - * under the License. - * - * The Original Code is BSAopt. - * - * The Initial Developer of the Original Code is - * Ethatron . Portions created by The Initial - * Developer are Copyright (C) 2011 The Initial Developer. - * All Rights Reserved. - * - * Alternatively, the contents of this file may be used under the terms - * of the GNU Library General Public License Version 3 license (the - * "LGPL License"), in which case the provisions of LGPL License are - * applicable instead of those above. If you wish to allow use of your - * version of this file only under the terms of the LGPL License and not - * to allow others to use your version of this file under the MPL, - * indicate your decision by deleting the provisions above and replace - * them with the notice and other provisions required by the LGPL License. - * If you do not delete the provisions above, a recipient may use your - * version of this file under either the MPL or the LGPL License." - */ -#ifndef BSAOPT_HASH_H -#define BSAOPT_HASH_H - -#include - -std::uint32_t GenOBHashStr(const std::string& s); - -std::uint64_t GenOBHashPair(const std::string& fle, const std::string& ext); - -std::uint64_t GenOBHash(const std::string& path, std::string& file); - -#endif // BSAOPT_HASH_H From 6aa6b2dc8909148791d969b51a056644b93853b1 Mon Sep 17 00:00:00 2001 From: Azdul Date: Fri, 18 Jan 2019 21:14:44 +0100 Subject: [PATCH 427/441] Removed TES4 from file names. Correct Git file history / attribution. --- components/CMakeLists.txt | 2 +- components/bsa/compressedbsafile.cpp | 425 +++++++++++++++++++++++++++ components/bsa/compressedbsafile.hpp | 101 +++++++ components/bsa/memorystream.cpp | 2 +- components/bsa/memorystream.hpp | 2 +- components/bsa/tes4bsa_file.cpp | 339 --------------------- components/bsa/tes4bsa_file.hpp | 103 ------- components/vfs/bsaarchive.cpp | 8 +- 8 files changed, 533 insertions(+), 449 deletions(-) create mode 100644 components/bsa/compressedbsafile.cpp create mode 100644 components/bsa/compressedbsafile.hpp delete mode 100644 components/bsa/tes4bsa_file.cpp delete mode 100644 components/bsa/tes4bsa_file.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index a289f6647..76641899d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -33,7 +33,7 @@ add_component_dir (settings ) add_component_dir (bsa - bsa_file tes4bsa_file memorystream + bsa_file compressedbsafile memorystream ) add_component_dir (vfs diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp new file mode 100644 index 000000000..598f65250 --- /dev/null +++ b/components/bsa/compressedbsafile.cpp @@ -0,0 +1,425 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (compressedbsafile.cpp) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + Compressed BSA stuff added by cc9cii 2018 + + */ +#include "compressedbsafile.hpp" + +#include +#include + +#include +#include +#include +#include + +#include // see: http://en.uesp.net/wiki/Tes4Mod:Hash_Calculation + +#include +#include +#include +#include +#include +#include + +namespace Bsa +{ +//special marker for invalid records, +//equal to max uint32_t value +const uint32_t CompressedBSAFile::sInvalidOffset = std::numeric_limits::max(); + +//bit marking compression on file size +const uint32_t CompressedBSAFile::sCompressedFlag = 1u << 30u; + + +CompressedBSAFile::FileRecord::FileRecord() : size(0), offset(sInvalidOffset) +{ } + +bool CompressedBSAFile::FileRecord::isValid() const +{ + return offset != sInvalidOffset; +} + +bool CompressedBSAFile::FileRecord::isCompressed(bool bsaCompressedByDefault) const +{ + bool compressionFlagEnabled = ((size & sCompressedFlag) == sCompressedFlag); + + if (bsaCompressedByDefault) { + return !compressionFlagEnabled; + } + return compressionFlagEnabled; +} + +std::uint32_t CompressedBSAFile::FileRecord::getSizeWithoutCompressionFlag() const { + return size & (~sCompressedFlag); +} + +void CompressedBSAFile::getBZString(std::string& str, std::istream& filestream) +{ + char size = 0; + filestream.read(&size, 1); + + boost::scoped_array buf(new char[size]); + filestream.read(buf.get(), size); + + if (buf[size - 1] != 0) + { + str.assign(buf.get(), size); + if (str.size() != ((size_t)size)) { + fail("getBZString string size mismatch"); + } + } + else + { + str.assign(buf.get(), size - 1); // don't copy null terminator + if (str.size() != ((size_t)size - 1)) { + fail("getBZString string size mismatch (null terminator)"); + } + } +} + +CompressedBSAFile::CompressedBSAFile() + : mCompressedByDefault(false), mEmbeddedFileNames(false) +{ } + +CompressedBSAFile::~CompressedBSAFile() +{ } + +/// Read header information from the input source +void CompressedBSAFile::readHeader() +{ + assert(!mIsLoaded); + + namespace bfs = boost::filesystem; + bfs::ifstream input(bfs::path(mFilename), std::ios_base::binary); + + // Total archive size + std::streamoff fsize = 0; + if(input.seekg(0, std::ios_base::end)) + { + fsize = input.tellg(); + input.seekg(0); + } + + if(fsize < 36) // header is 36 bytes + fail("File too small to be a valid BSA archive"); + + // Get essential header numbers + //size_t dirsize, filenum; + std::uint32_t archiveFlags, folderCount, totalFileNameLength; + { + // First 36 bytes + std::uint32_t header[9]; + + input.read(reinterpret_cast(header), 36); + + if(header[0] != 0x00415342 /*"BSA\x00"*/ || (header[1] != 0x67 /*TES4*/ && header[1] != 0x68 /*TES5*/)) + fail("Unrecognized TES4 BSA header"); + + // header[2] is offset, should be 36 = 0x24 which is the size of the header + + // Oblivion - Meshes.bsa + // + // 0111 1000 0111 = 0x0787 + // ^^^ ^ ^^^ + // ||| | ||+-- has names for dirs (mandatory?) + // ||| | |+--- has names for files (mandatory?) + // ||| | +---- files are compressed by default + // ||| | + // ||| +---------- unknown (TES5: retain strings during startup) + // ||+------------ unknown (TES5: embedded file names) + // |+------------- unknown + // +-------------- unknown + // + archiveFlags = header[3]; + folderCount = header[4]; + // header[5] - fileCount + // totalFolderNameLength = header[6]; + totalFileNameLength = header[7]; + // header[8]; // fileFlags : an opportunity to optimize here + + mCompressedByDefault = (archiveFlags & 0x4) != 0; + mEmbeddedFileNames = header[1] == 0x68 /*TES5*/ && (archiveFlags & 0x100) != 0; + } + + // folder records + std::uint64_t hash; + FolderRecord fr; + for (std::uint32_t i = 0; i < folderCount; ++i) + { + input.read(reinterpret_cast(&hash), 8); + input.read(reinterpret_cast(&fr.count), 4); // not sure purpose of count + input.read(reinterpret_cast(&fr.offset), 4); // not sure purpose of offset + + std::map::const_iterator lb = mFolders.lower_bound(hash); + if (lb != mFolders.end() && !(mFolders.key_comp()(hash, lb->first))) + fail("Archive found duplicate folder name hash"); + else + mFolders.insert(lb, std::pair(hash, fr)); + } + + // file record blocks + std::uint64_t fileHash; + FileRecord file; + + std::string folder(""); + std::uint64_t folderHash; + if ((archiveFlags & 0x1) == 0) + folderCount = 1; // TODO: not tested - unit test necessary + + mFiles.clear(); + std::vector fullPaths; + + for (std::uint32_t i = 0; i < folderCount; ++i) + { + if ((archiveFlags & 0x1) != 0) + getBZString(folder, input); + + std::string emptyString; + folderHash = GenOBHash(folder, emptyString); + + std::map::iterator iter = mFolders.find(folderHash); + if (iter == mFolders.end()) + fail("Archive folder name hash not found"); + + for (std::uint32_t j = 0; j < iter->second.count; ++j) + { + input.read(reinterpret_cast(&fileHash), 8); + input.read(reinterpret_cast(&file.size), 4); + input.read(reinterpret_cast(&file.offset), 4); + + std::map::const_iterator lb = iter->second.files.lower_bound(fileHash); + if (lb != iter->second.files.end() && !(iter->second.files.key_comp()(fileHash, lb->first))) + fail("Archive found duplicate file name hash"); + + iter->second.files.insert(lb, std::pair(fileHash, file)); + + FileStruct fileStruct; + fileStruct.fileSize = file.getSizeWithoutCompressionFlag(); + fileStruct.offset = file.offset; + fileStruct.name = nullptr; + mFiles.push_back(fileStruct); + + fullPaths.push_back(folder); + } + } + + // file record blocks + if ((archiveFlags & 0x2) != 0) + { + mStringBuf.resize(totalFileNameLength); + input.read(&mStringBuf[0], mStringBuf.size()); // TODO: maybe useful in building a lookup map? + } + + size_t mStringBuffOffset = 0; + size_t totalStringsSize = 0; + for (std::uint32_t fileIndex = 0; fileIndex < mFiles.size(); ++fileIndex) { + + if (mStringBuffOffset >= totalFileNameLength) { + fail("Corrupted names record in BSA file"); + } + + //The vector guarantees that its elements occupy contiguous memory + mFiles[fileIndex].name = reinterpret_cast(mStringBuf.data() + mStringBuffOffset); + + fullPaths.at(fileIndex) += "\\" + std::string(mStringBuf.data() + mStringBuffOffset); + + while (mStringBuffOffset < totalFileNameLength) { + if (mStringBuf[mStringBuffOffset] != '\0') { + mStringBuffOffset++; + } + else { + mStringBuffOffset++; + break; + } + } + //we want to keep one more 0 character at the end of each string + totalStringsSize += fullPaths.at(fileIndex).length() + 1u; + } + mStringBuf.resize(totalStringsSize); + + mStringBuffOffset = 0; + for (std::uint32_t fileIndex = 0u; fileIndex < mFiles.size(); fileIndex++) { + size_t stringLength = fullPaths.at(fileIndex).length(); + + std::copy(fullPaths.at(fileIndex).c_str(), + //plus 1 because we also want to copy 0 at the end of the string + fullPaths.at(fileIndex).c_str() + stringLength + 1u, + mStringBuf.data() + mStringBuffOffset); + + mFiles[fileIndex].name = reinterpret_cast(mStringBuf.data() + mStringBuffOffset); + + mLookup[reinterpret_cast(mStringBuf.data() + mStringBuffOffset)] = fileIndex; + mStringBuffOffset += stringLength + 1u; + } + + if (mStringBuffOffset != mStringBuf.size()) { + fail("Could not resolve names of files in BSA file"); + } + + convertCompressedSizesToUncompressed(); + mIsLoaded = true; +} + +CompressedBSAFile::FileRecord CompressedBSAFile::getFileRecord(const std::string& str) const +{ + boost::filesystem::path p(str); + std::string stem = p.stem().string(); + std::string ext = p.extension().string(); + std::string filename = p.filename().string(); + p.remove_filename(); + + std::string folder = p.string(); + // GenOBHash already converts to lowercase and replaces file separators but not for path + boost::algorithm::to_lower(folder); + std::replace(folder.begin(), folder.end(), '/', '\\'); + + std::string emptyString; + std::uint64_t folderHash = GenOBHash(folder, emptyString); + + std::map::const_iterator it = mFolders.find(folderHash); + if (it == mFolders.end()) + return FileRecord(); // folder not found, return default which has offset of sInvalidOffset + + boost::algorithm::to_lower(stem); + boost::algorithm::to_lower(ext); + std::uint64_t fileHash = GenOBHashPair(stem, ext); + std::map::const_iterator iter = it->second.files.find(fileHash); + if (iter == it->second.files.end()) + return FileRecord(); // file not found, return default which has offset of sInvalidOffset + + return iter->second; +} + +Files::IStreamPtr CompressedBSAFile::getFile(const FileStruct* file) +{ + FileRecord fileRec = getFileRecord(file->name); + if (!fileRec.isValid()) { + fail("File not found: " + std::string(file->name)); + } + return getFile(fileRec); +} + +Files::IStreamPtr CompressedBSAFile::getFile(const char* file) +{ + FileRecord fileRec = getFileRecord(file); + if (!fileRec.isValid()) { + fail("File not found: " + std::string(file)); + } + return getFile(fileRec); +} + +Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord) +{ + if (fileRecord.isCompressed(mCompressedByDefault)) { + Files::IStreamPtr streamPtr = Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.getSizeWithoutCompressionFlag()); + + std::istream* fileStream = streamPtr.get(); + + if (mEmbeddedFileNames) { + std::string embeddedFileName; + getBZString(embeddedFileName, *fileStream); + } + + uint32_t uncompressedSize = 0u; + fileStream->read(reinterpret_cast(&uncompressedSize), sizeof(uncompressedSize)); + + boost::iostreams::filtering_streambuf inputStreamBuf; + inputStreamBuf.push(boost::iostreams::zlib_decompressor()); + inputStreamBuf.push(*fileStream); + + std::shared_ptr memoryStreamPtr = std::make_shared(uncompressedSize); + + boost::iostreams::basic_array_sink sr(memoryStreamPtr->getRawData(), uncompressedSize); + boost::iostreams::copy(inputStreamBuf, sr); + + return std::shared_ptr(memoryStreamPtr, (std::istream*)memoryStreamPtr.get()); + } + + return Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.size); +} + +BsaVersion CompressedBSAFile::detectVersion(std::string filePath) +{ + namespace bfs = boost::filesystem; + bfs::ifstream input(bfs::path(filePath), std::ios_base::binary); + + // Total archive size + std::streamoff fsize = 0; + if (input.seekg(0, std::ios_base::end)) + { + fsize = input.tellg(); + input.seekg(0); + } + + if (fsize < 12) { + return BSAVER_UNKNOWN; + } + + // Get essential header numbers + + // First 12 bytes + uint32_t head[3]; + + input.read(reinterpret_cast(head), 12); + + if (head[0] == static_cast(BSAVER_UNCOMPRESSED)) { + return BSAVER_UNCOMPRESSED; + } + + if (head[0] == static_cast(BSAVER_COMPRESSED)) { + return BSAVER_COMPRESSED; + } + + return BSAVER_UNKNOWN; +} + +//mFiles used by OpenMW expects uncompressed sizes +void CompressedBSAFile::convertCompressedSizesToUncompressed() +{ + for (auto iter = mFiles.begin(); iter != mFiles.end(); ++iter) + { + const FileRecord& fileRecord = getFileRecord(iter->name); + if (!fileRecord.isValid()) + { + fail("Could not find file " + std::string(iter->name) + " in BSA"); + } + + if (!fileRecord.isCompressed(mCompressedByDefault)) + { + //no need to fix fileSize in mFiles - uncompressed size already set + continue; + } + + Files::IStreamPtr dataBegin = Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.getSizeWithoutCompressionFlag()); + + if (mEmbeddedFileNames) + { + std::string embeddedFileName; + getBZString(embeddedFileName, *(dataBegin.get())); + } + + dataBegin->read(reinterpret_cast(&(iter->fileSize)), sizeof(iter->fileSize)); + } +} + +} //namespace Bsa diff --git a/components/bsa/compressedbsafile.hpp b/components/bsa/compressedbsafile.hpp new file mode 100644 index 000000000..b607eb3ef --- /dev/null +++ b/components/bsa/compressedbsafile.hpp @@ -0,0 +1,101 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (compressedbsafile.hpp) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + Compressed BSA stuff added by cc9cii 2018 + + */ + +#ifndef BSA_COMPRESSED_BSA_FILE_H +#define BSA_COMPRESSED_BSA_FILE_H + +#include +#include +#include +#include +#include + +namespace Bsa +{ + enum BsaVersion + { + BSAVER_UNKNOWN = 0x0, + BSAVER_UNCOMPRESSED = 0x100, + BSAVER_COMPRESSED = 0x415342 //B, S, A + }; + + class CompressedBSAFile : public BSAFile + { + private: + //special marker for invalid records, + //equal to max uint32_t value + static const uint32_t sInvalidOffset; + + //bit marking compression on file size + static const uint32_t sCompressedFlag; + + struct FileRecord + { + std::uint32_t size; + std::uint32_t offset; + + FileRecord(); + bool isCompressed(bool bsaCompressedByDefault) const; + bool isValid() const; + std::uint32_t getSizeWithoutCompressionFlag() const; + }; + + //if files in BSA without 30th bit enabled are compressed + bool mCompressedByDefault; + + //if each file record begins with BZ string with file name + bool mEmbeddedFileNames; + + struct FolderRecord + { + std::uint32_t count; + std::uint32_t offset; + std::map files; + }; + std::map mFolders; + + FileRecord getFileRecord(const std::string& str) const; + + void getBZString(std::string& str, std::istream& filestream); + //mFiles used by OpenMW will contain uncompressed file sizes + void convertCompressedSizesToUncompressed(); + Files::IStreamPtr getFile(const FileRecord& fileRecord); + public: + CompressedBSAFile(); + virtual ~CompressedBSAFile(); + + //checks version of BSA from file header + static BsaVersion detectVersion(std::string filePath); + + /// Read header information from the input source + virtual void readHeader(); + + Files::IStreamPtr getFile(const char* filePath); + Files::IStreamPtr getFile(const FileStruct* fileStruct); + + }; +} + +#endif diff --git a/components/bsa/memorystream.cpp b/components/bsa/memorystream.cpp index 3cacad60f..34e98e6b6 100644 --- a/components/bsa/memorystream.cpp +++ b/components/bsa/memorystream.cpp @@ -19,7 +19,7 @@ version 3 along with this program. If not, see http://www.gnu.org/licenses/ . - TES4 stuff upgrade added by Azdul 2019 + Compressed BSA upgrade added by Azdul 2019 */ #include "memorystream.hpp" diff --git a/components/bsa/memorystream.hpp b/components/bsa/memorystream.hpp index 0182e901a..5dbe16ebe 100644 --- a/components/bsa/memorystream.hpp +++ b/components/bsa/memorystream.hpp @@ -19,7 +19,7 @@ version 3 along with this program. If not, see http://www.gnu.org/licenses/ . - TES4 stuff upgrade added by Azdul 2019 + Compressed BSA upgrade added by Azdul 2019 */ diff --git a/components/bsa/tes4bsa_file.cpp b/components/bsa/tes4bsa_file.cpp deleted file mode 100644 index 33caefd0b..000000000 --- a/components/bsa/tes4bsa_file.cpp +++ /dev/null @@ -1,339 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (bsa_file.cpp) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - TES4 stuff added by cc9cii 2018 - - */ -#include "tes4bsa_file.hpp" - -#include -#include - -#include -#include -#include -#include - -#include - -#include // see: http://en.uesp.net/wiki/Tes4Mod:Hash_Calculation - -#undef TEST_UNIQUE_HASH - -namespace -{ - void getBZString(std::string& str, boost::filesystem::ifstream& filestream) - { - char size = 0; - filestream.read(&size, 1); - - boost::scoped_array buf(new char[size]); - filestream.read(buf.get(), size); - - if (buf[size - 1] != 0) - str.assign(buf.get(), size); - else - str.assign(buf.get(), size - 1); // don't copy null terminator - assert((size_t)size-1 == str.size() && "getBZString string size mismatch"); - return; - } -} - -using namespace Bsa; - - -/// Error handling -void TES4BSAFile::fail(const std::string& msg) -{ - throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename); -} - -/// Read header information from the input source -void TES4BSAFile::readHeader() -{ - assert(!isLoaded); - - namespace bfs = boost::filesystem; - bfs::ifstream input(bfs::path(mFilename), std::ios_base::binary); - - // Total archive size - std::streamoff fsize = 0; - if(input.seekg(0, std::ios_base::end)) - { - fsize = input.tellg(); - input.seekg(0); - } - - if(fsize < 36) // header is 36 bytes - fail("File too small to be a valid BSA archive"); - - // Get essential header numbers - //size_t dirsize, filenum; - std::uint32_t archiveFlags, folderCount, fileCount, totalFileNameLength; - { - // First 36 bytes - std::uint32_t header[9]; - - input.read(reinterpret_cast(header), 36); - - if(header[0] != 0x00415342 /*"BSA\x00"*/ || (header[1] != 0x67 /*TES4*/ && header[1] != 0x68 /*TES5*/)) - fail("Unrecognized TES4 BSA header"); - - // header[2] is offset, should be 36 = 0x24 which is the size of the header - - // Oblivion - Meshes.bsa - // - // 0111 1000 0111 = 0x0787 - // ^^^ ^ ^^^ - // ||| | ||+-- has names for dirs (mandatory?) - // ||| | |+--- has names for files (mandatory?) - // ||| | +---- files are compressed by default - // ||| | - // ||| +---------- unknown (TES5: retain strings during startup) - // ||+------------ unknown (TES5: embedded file names) - // |+------------- unknown - // +-------------- unknown - // - archiveFlags = header[3]; - folderCount = header[4]; - fileCount = header[5]; - //totalFolderNameLength = header[6]; - totalFileNameLength = header[7]; - //fileFlags = header[8]; // FIXME: an opportunity to optimize here - - mCompressedByDefault = (archiveFlags & 0x4) != 0; - mEmbeddedFileNames = header[1] == 0x68 /*TES5*/ && (archiveFlags & 0x100) != 0; - } - - // TODO: more checks for BSA file corruption - - // folder records - std::uint64_t hash; - FolderRecord fr; - for (std::uint32_t i = 0; i < folderCount; ++i) - { - input.read(reinterpret_cast(&hash), 8); - input.read(reinterpret_cast(&fr.count), 4); // not sure purpose of count - input.read(reinterpret_cast(&fr.offset), 4); // not sure purpose of offset - - std::map::const_iterator lb = mFolders.lower_bound(hash); - if (lb != mFolders.end() && !(mFolders.key_comp()(hash, lb->first))) - fail("Archive found duplicate folder name hash"); - else - mFolders.insert(lb, std::pair(hash, fr)); - } - - // file record blocks - std::uint64_t fileHash; - FileRecord file; - - std::string folder(""); - std::uint64_t folderHash; - if ((archiveFlags & 0x1) == 0) - folderCount = 1; // TODO: not tested - - for (std::uint32_t i = 0; i < folderCount; ++i) - { - if ((archiveFlags & 0x1) != 0) - getBZString(folder, input); - - folderHash = GenOBHash(folder, std::string("")); - - std::map::iterator iter = mFolders.find(folderHash); - if (iter == mFolders.end()) - fail("Archive folder name hash not found"); - - for (std::uint32_t j = 0; j < iter->second.count; ++j) - { - input.read(reinterpret_cast(&fileHash), 8); - input.read(reinterpret_cast(&file.size), 4); - input.read(reinterpret_cast(&file.offset), 4); - - std::map::const_iterator lb = iter->second.files.lower_bound(fileHash); - if (lb != iter->second.files.end() && !(iter->second.files.key_comp()(fileHash, lb->first))) - fail("Archive found duplicate file name hash"); - - iter->second.files.insert(lb, std::pair(fileHash, file)); - } - } - - // file record blocks - if ((archiveFlags & 0x2) != 0) - { - mStringBuf.resize(totalFileNameLength); - input.read(&mStringBuf[0], mStringBuf.size()); // TODO: maybe useful in building a lookup map? - } - - // TODO: more checks for BSA file corruption - - isLoaded = true; -} - -TES4BSAFile::FileRecord TES4BSAFile::getFileRecord(const std::string& str) const -{ - boost::filesystem::path p(str); - std::string stem = p.stem().string(); - std::string ext = p.extension().string(); - std::string filename = p.filename().string(); - p.remove_filename(); - - std::string folder = p.string(); - // GenOBHash already converts to lowercase and replaces file separators but not for path - boost::algorithm::to_lower(folder); - std::replace(folder.begin(), folder.end(), '/', '\\'); - - std::uint64_t folderHash = GenOBHash(folder, std::string("")); - - std::map::const_iterator it = mFolders.find(folderHash); - if (it == mFolders.end()) - return FileRecord(); // folder not found, return default which has offset of -1 - - boost::algorithm::to_lower(stem); - boost::algorithm::to_lower(ext); - std::uint64_t fileHash = GenOBHashPair(stem, ext); - std::map::const_iterator iter = it->second.files.find(fileHash); - if (iter == it->second.files.end()) - return FileRecord(); // file not found, return default which has offset of -1 - - // cache for next time - std::uint64_t hash = GenOBHash(folder, filename); - -#if defined (TEST_UNIQUE_HASH) - FileList::const_iterator lb = mFiles.lower_bound(hash); - if (lb != mFiles.end() && !(mFiles.key_comp()(hash, lb->first))) - { - // found, check if same filename - if (lb->second.fileName == str) - return iter->second; // same name, should not have got here!! - else - { - // different filename, hash is not unique! - std::cerr << "BSA hash collision: " << str << std::hex << "0x" << hash << std::endl; - - return iter->second; // return without cashing - } - } - - // not found, cache for later - const_cast(mFiles).insert(lb, std::pair(hash, iter->second)); - const_cast(mFiles)[hash].fileName = str; -#else - const_cast(mFiles)[hash] = iter->second; // NOTE: const hack -#endif - return iter->second; -} - -bool TES4BSAFile::exists(const std::string& str) const -{ - // check cache first - boost::filesystem::path p(str); - std::string filename = p.filename().string(); - p.remove_filename(); - - std::string folder = p.string(); - // GenOBHash already converts to lowercase and replaces file separators but not for path - boost::algorithm::to_lower(folder); - std::replace(folder.begin(), folder.end(), '/', '\\'); - - std::uint64_t hash = GenOBHash(folder, filename); - - std::map::const_iterator it = mFiles.find(hash); -#if defined (TEST_UNIQUE_HASH) - if (it != mFiles.end() && it->second.fileName == str) -#else - if (it != mFiles.end()) -#endif - return true; - else - return getFileRecord(str).offset != -1; -} - -void TES4BSAFile::open(const std::string& file) -{ - mFilename = file; - readHeader(); -} - -Ogre::DataStreamPtr TES4BSAFile::getFile(const std::string& file) -{ - assert(file); - - FileRecord fileRec = getFileRecord(file); - if(fileRec.offset == -1) - fail("File not found: " + std::string(file)); - - boost::filesystem::ifstream input(boost::filesystem::path(mFilename), std::ios_base::binary); - input.seekg(fileRec.offset); - - std::string fullPath; - if (mEmbeddedFileNames) - getBZString(fullPath, input); // TODO: maybe cache the hash and/or offset of frequently used ones? - - if (( mCompressedByDefault && (fileRec.size & (1<<30)) == 0) - || - (!mCompressedByDefault && (fileRec.size & (1<<30)) != 0)) - { - std::uint32_t bufSize = 0; - boost::scoped_array inBuf; - inBuf.reset(new unsigned char[fileRec.size-4]); - input.read(reinterpret_cast(&bufSize), 4); - input.read(reinterpret_cast(inBuf.get()), fileRec.size-4); - Ogre::MemoryDataStream *outBuf = new Ogre::MemoryDataStream(bufSize); - Ogre::SharedPtr streamPtr(outBuf); - - int ret; - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = bufSize; - strm.next_in = inBuf.get(); - ret = inflateInit(&strm); - if (ret != Z_OK) - throw std::runtime_error("TES4BSAFile::getFile - inflateInit failed"); - - strm.avail_out = bufSize; - strm.next_out = outBuf->getPtr(); - ret = inflate(&strm, Z_NO_FLUSH); - assert(ret != Z_STREAM_ERROR && "TES4BSAFile::getFile - inflate - state clobbered"); - switch (ret) - { - case Z_NEED_DICT: - ret = Z_DATA_ERROR; /* and fall through */ - case Z_DATA_ERROR: - case Z_MEM_ERROR: - inflateEnd(&strm); - throw std::runtime_error("TES4BSAFile::getFile - inflate failed"); - } - assert(ret == Z_OK || ret == Z_STREAM_END); - inflateEnd(&strm); - - return streamPtr; - } - else // not compressed TODO: not tested - { - Ogre::MemoryDataStream *outBuf = new Ogre::MemoryDataStream(fileRec.size); - Ogre::SharedPtr streamPtr(outBuf); - input.read(reinterpret_cast(outBuf->getPtr()), fileRec.size); - - return streamPtr; - } -} diff --git a/components/bsa/tes4bsa_file.hpp b/components/bsa/tes4bsa_file.hpp deleted file mode 100644 index 979a6af1a..000000000 --- a/components/bsa/tes4bsa_file.hpp +++ /dev/null @@ -1,103 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (bsa_file.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - TES4 stuff added by cc9cii 2018 - - */ - -#ifndef BSA_TES4BSA_FILE_H -#define BSA_TES4BSA_FILE_H - -#include -#include -#include -#include - -#include - - -namespace Bsa -{ - class TES4BSAFile - { - public: - struct FileRecord - { - std::uint32_t size; - std::uint32_t offset; - - std::string fileName; // NOTE: for testing hash collision only, see TEST_UNIQUE_HASH - - FileRecord() : size(0), offset(-1) {} - }; - - private: - /// Filenames string buffer - std::vector mStringBuf; - - /// True when an archive has been loaded - bool isLoaded; - - bool mCompressedByDefault; - bool mEmbeddedFileNames; - - std::map mFiles; - typedef std::map FileList; - - struct FolderRecord - { - std::uint32_t count; - std::uint32_t offset; - std::map files; - }; - std::map mFolders; - - FileRecord getFileRecord(const std::string& str) const; - - /// Used for error messages and getting files - std::string mFilename; - - /// Error handling - void fail(const std::string &msg); - - /// Read header information from the input source - void readHeader(); - - public: - TES4BSAFile() - : isLoaded(false), mCompressedByDefault(false), mEmbeddedFileNames(false) - { } - - /// Open an archive file. - void open(const std::string &file); - - /// Check if a file exists - bool exists(const std::string& file) const; - - Ogre::DataStreamPtr getFile(const std::string& file); - - /// Get a list of all files - const FileList &getList() const // FIXME - { return mFiles; } - }; -} - -#endif diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index 268a53047..925c1076e 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -1,5 +1,5 @@ #include "bsaarchive.hpp" -#include +#include #include namespace VFS @@ -7,10 +7,10 @@ namespace VFS BsaArchive::BsaArchive(const std::string &filename) { - Bsa::BsaVersion bsaVersion = Bsa::TES4BSAFile::detectVersion(filename); + Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(filename); - if (bsaVersion == Bsa::BSAVER_TES4PLUS) { - mFile = std::unique_ptr(new Bsa::TES4BSAFile()); + if (bsaVersion == Bsa::BSAVER_COMPRESSED) { + mFile = std::unique_ptr(new Bsa::CompressedBSAFile()); } else { mFile = std::unique_ptr(new Bsa::BSAFile()); From a8c1c4315cb461dc3889f49527e50bb05fafbd42 Mon Sep 17 00:00:00 2001 From: Azdul Date: Fri, 18 Jan 2019 21:19:59 +0100 Subject: [PATCH 428/441] Missing header on Linux --- extern/BSAOpt/hash.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/extern/BSAOpt/hash.cpp b/extern/BSAOpt/hash.cpp index 029c54279..08c807da4 100644 --- a/extern/BSAOpt/hash.cpp +++ b/extern/BSAOpt/hash.cpp @@ -31,6 +31,7 @@ #include #include +#include #include "hash.hpp" From 7880e6f7612b70a50d6bef9fc404fd43decff49e Mon Sep 17 00:00:00 2001 From: Azdul Date: Sat, 19 Jan 2019 14:41:17 +0100 Subject: [PATCH 429/441] CompressedBSAFile::FileRecord::isCompressed implementation simplified. Double space in CompressedBSAFile::FileRecord::getSizeWithoutCompressionFlag removed. --- components/bsa/compressedbsafile.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index 598f65250..9ff0b67ce 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -61,16 +61,19 @@ bool CompressedBSAFile::FileRecord::isValid() const bool CompressedBSAFile::FileRecord::isCompressed(bool bsaCompressedByDefault) const { - bool compressionFlagEnabled = ((size & sCompressedFlag) == sCompressedFlag); - - if (bsaCompressedByDefault) { - return !compressionFlagEnabled; - } - return compressionFlagEnabled; + bool recordCompressionFlagEnabled = ((size & sCompressedFlag) == sCompressedFlag); + + //record is compressed when: + //- bsaCompressedByDefault flag is set and 30th bit is NOT set, or + //- bsaCompressedByDefault flag is NOT set and 30th bit is set + //record is NOT compressed when: + //- bsaCompressedByDefault flag is NOT set and 30th bit is NOT set, or + //- bsaCompressedByDefault flag is set and 30th bit is set + return (bsaCompressedByDefault != recordCompressionFlagEnabled); } std::uint32_t CompressedBSAFile::FileRecord::getSizeWithoutCompressionFlag() const { - return size & (~sCompressedFlag); + return size & (~sCompressedFlag); } void CompressedBSAFile::getBZString(std::string& str, std::istream& filestream) From 4a266b32775b3c00eef7faff935201dca20b2cdf Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 20 Jan 2019 11:03:37 +0100 Subject: [PATCH 430/441] [macOS, CI] Update dependencies They now include boost iostreams for TES 4/5 BSA support. See https://gitlab.com/OpenMW/openmw/merge_requests/56. --- CI/before_install.osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 43102356e..e411a1d9b 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -7,5 +7,5 @@ brew switch cmake 3.12.4 brew outdated pkgconfig || brew upgrade pkgconfig brew install qt -curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-55c27b1.zip -o ~/openmw-deps.zip +curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-110f3d3.zip -o ~/openmw-deps.zip unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null From 1e8b7f0f839813f9c95ebb1442364a3e9c2ece7d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 20 Jan 2019 15:46:19 +0400 Subject: [PATCH 431/441] Fix -Wstring-plus-int warnings --- apps/opencs/model/tools/search.cpp | 2 +- apps/openmw/mwmechanics/alchemy.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp index 9f2edfb93..e2cfd177b 100644 --- a/apps/opencs/model/tools/search.cpp +++ b/apps/opencs/model/tools/search.cpp @@ -78,7 +78,7 @@ void CSMTools::Search::searchRecordStateCell (const CSMWorld::IdTableBase *model std::vector states = CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); - const std::string hint = "r: " + model->getColumnId (index.column()); + const std::string hint = "r: " + std::to_string(model->getColumnId(index.column())); messages.add (id, states.at(data), hint); } } diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index a4434e7db..facac4291 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -154,7 +154,7 @@ void MWMechanics::Alchemy::updateEffects() if (magicEffect->mData.mBaseCost<=0) { - const std::string os = "invalid base cost for magic effect " + iter->mId; + const std::string os = "invalid base cost for magic effect " + std::to_string(iter->mId); throw std::runtime_error (os); } From 3572edab72d3b9b2a534afd657be02fd93522038 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 20 Jan 2019 19:27:52 +0300 Subject: [PATCH 432/441] Update navmesh in a separate method --- apps/openmw/mwrender/renderingmanager.cpp | 48 +++++++++++++---------- apps/openmw/mwrender/renderingmanager.hpp | 2 + 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4d79fd3de..693491efe 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -599,28 +599,8 @@ namespace MWRender mWater->update(dt); } - const auto navMeshes = mNavigator.getNavMeshes(); + updateNavMesh(); - auto it = navMeshes.begin(); - for (std::size_t i = 0; it != navMeshes.end() && i < mNavMeshNumber; ++i) - ++it; - if (it == navMeshes.end()) - { - mNavMesh->reset(); - } - else - { - try - { - const auto locked = it->second.lockConst(); - mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(), - locked->getNavMeshRevision(), mNavigator.getSettings()); - } - catch (const std::exception& e) - { - Log(Debug::Error) << "NavMesh render update exception: " << e.what(); - } - } mCamera->update(dt, paused); osg::Vec3f focal, cameraPos; @@ -1402,4 +1382,30 @@ namespace MWRender { mNavMeshNumber = value; } + + void RenderingManager::updateNavMesh() + { + const auto navMeshes = mNavigator.getNavMeshes(); + + auto it = navMeshes.begin(); + for (std::size_t i = 0; it != navMeshes.end() && i < mNavMeshNumber; ++i) + ++it; + if (it == navMeshes.end()) + { + mNavMesh->reset(); + } + else + { + try + { + const auto locked = it->second.lockConst(); + mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(), + locked->getNavMeshRevision(), mNavigator.getSettings()); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "NavMesh render update exception: " << e.what(); + } + } + } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 7a4500ac9..2a48097bd 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -240,6 +240,8 @@ namespace MWRender void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); + void updateNavMesh(); + osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); osg::ref_ptr mIntersectionVisitor; From 13e94ab19452f2de53c4dbf5e7e92a2d0e5e04b6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 20 Jan 2019 19:30:26 +0300 Subject: [PATCH 433/441] Get navmesh to update osg node only if rendering is enabled --- apps/openmw/mwrender/navmesh.hpp | 5 +++++ apps/openmw/mwrender/renderingmanager.cpp | 3 +++ 2 files changed, 8 insertions(+) diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index 29205ca27..d329b895d 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -30,6 +30,11 @@ namespace MWRender void disable(); + bool isEnabled() const + { + return mEnabled; + } + private: osg::ref_ptr mRootNode; bool mEnabled; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 693491efe..322c310a2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1385,6 +1385,9 @@ namespace MWRender void RenderingManager::updateNavMesh() { + if (!mNavMesh->isEnabled()) + return; + const auto navMeshes = mNavigator.getNavMeshes(); auto it = navMeshes.begin(); From b4a78a7e68b2fe7aa6873d8ab5041a257cb6592d Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sun, 20 Jan 2019 21:18:52 +0100 Subject: [PATCH 434/441] add libboost-iostreams-dev --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 18a9949d1..51b1720c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ addons: # Dev cmake, clang-7, clang-tools-7, gcc-8, g++-8, # Boost - libboost-filesystem-dev, libboost-program-options-dev, libboost-system-dev, + libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev, # FFmpeg libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev, # Audio, Video and Misc. deps From be5faadff84547b54e601cedc9ed06708d47c9ba Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 21 Jan 2019 01:21:12 +0300 Subject: [PATCH 435/441] Fix compressed BSA loading on non-Windows systems --- components/bsa/compressedbsafile.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index 9ff0b67ce..40dbc2856 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -285,10 +285,15 @@ void CompressedBSAFile::readHeader() CompressedBSAFile::FileRecord CompressedBSAFile::getFileRecord(const std::string& str) const { - boost::filesystem::path p(str); + // Force-convert the path into something both Windows and UNIX can handle first + // to make sure Boost doesn't think the entire path is the filename on Linux + // and subsequently purge it to determine the file folder. + std::string path = str; + std::replace(path.begin(), path.end(), '\\', '/'); + + boost::filesystem::path p(path); std::string stem = p.stem().string(); std::string ext = p.extension().string(); - std::string filename = p.filename().string(); p.remove_filename(); std::string folder = p.string(); From 71be4cdd20657b616c772e1f074f157c65e24c45 Mon Sep 17 00:00:00 2001 From: Stomy Date: Mon, 21 Jan 2019 19:30:11 +0000 Subject: [PATCH 436/441] Ensure Unique Instance Refnum after clone, add, or move --- apps/opencs/model/doc/savingstages.cpp | 14 ++++-- apps/opencs/model/world/collection.hpp | 6 +++ apps/opencs/model/world/commanddispatcher.cpp | 44 +++++++++++++------ apps/opencs/view/render/object.cpp | 41 +++++++++++------ 4 files changed, 74 insertions(+), 31 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index f9c028e29..b7eb369d7 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -269,7 +269,7 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) bool interior = cellRecord.mId.substr (0, 1)!="#"; // count new references and adjust RefNumCount accordingsly - int newRefNum = cellRecord.mRefNumCounter; + unsigned int newRefNum = cellRecord.mRefNumCounter; if (references!=mState.getSubRecords().end()) { @@ -279,11 +279,17 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) const CSMWorld::Record& ref = mDocument.getData().getReferences().getRecord (*iter); - if (ref.get().mNew || + CSMWorld::CellRef refRecord = ref.get(); + + if (refRecord.mNew || (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && /// \todo consider worldspace - CSMWorld::CellCoordinates (ref.get().getCellIndex()).getId("")!=ref.get().mCell)) + CSMWorld::CellCoordinates (refRecord.getCellIndex()).getId("") != refRecord.mCell)) ++cellRecord.mRefNumCounter; + + if (refRecord.mRefNum.mIndex >= newRefNum) + newRefNum = refRecord.mRefNum.mIndex + 1; + } } @@ -328,7 +334,7 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) stream << "#" << index.first << " " << index.second; } - if (refRecord.mNew || + if (refRecord.mNew || refRecord.mRefNum.mIndex == 0 || (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && refRecord.mCell!=stream.str())) { diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 80117d0c6..3d6802b86 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -17,6 +17,7 @@ #include "collectionbase.hpp" #include "land.hpp" #include "landtexture.hpp" +#include "ref.hpp" namespace CSMWorld { @@ -259,6 +260,11 @@ namespace CSMWorld copy.mState = RecordBase::State_ModifiedOnly; IdAccessorT().setId(copy.get(), destination); + if (type == UniversalId::Type_Reference) { + CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) ©.mModified; + ptr->mRefNum.mIndex = 0; + } + int index = getAppendIndex(destination, type); insertRecord(copy, getAppendIndex(destination, type)); diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index 5d6a35cc5..2493813a6 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "../doc/document.hpp" @@ -140,29 +141,44 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons std::unique_ptr modifyCell; + std::unique_ptr modifyDataRefNum; + int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); if (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos) { - IdTableProxyModel *proxy = dynamic_cast (model); - - int row = proxy ? proxy->mapToSource (index).row() : index.row(); + const float oldPosition = model->data (index).toFloat(); - // This is not guaranteed to be the same as \a model, since a proxy could be used. - IdTable& model2 = dynamic_cast (*mDocument.getData().getTableModel (mId)); + // Modulate by cell size, update cell id if reference has been moved to a new cell + if (std::abs(std::fmod(oldPosition, Constants::CellSizeInUnits)) + - std::abs(std::fmod(new_.toFloat(), Constants::CellSizeInUnits)) >= 0.5f) + { + IdTableProxyModel *proxy = dynamic_cast (model); - int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell); + int row = proxy ? proxy->mapToSource (index).row() : index.row(); - if (cellColumn!=-1) - { - QModelIndex cellIndex = model2.index (row, cellColumn); + // This is not guaranteed to be the same as \a model, since a proxy could be used. + IdTable& model2 = dynamic_cast (*mDocument.getData().getTableModel (mId)); - std::string cellId = model2.data (cellIndex).toString().toUtf8().data(); + int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell); - if (cellId.find ('#')!=std::string::npos) + if (cellColumn!=-1) { - // Need to recalculate the cell - modifyCell.reset (new UpdateCellCommand (model2, row)); + QModelIndex cellIndex = model2.index (row, cellColumn); + + std::string cellId = model2.data (cellIndex).toString().toUtf8().data(); + + if (cellId.find ('#')!=std::string::npos) + { + // Need to recalculate the cell and (if necessary) clear the instance's refNum + modifyCell.reset (new UpdateCellCommand (model2, row)); + + // Not sure which model this should be applied to + int refNumColumn = model2.searchColumnIndex (Columns::ColumnId_RefNum); + + if (refNumColumn!=-1) + modifyDataRefNum.reset (new ModifyCommand(*model, model->index(row, refNumColumn), 0)); + } } } } @@ -175,6 +191,8 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons CommandMacro macro (mDocument.getUndoStack()); macro.push (modifyData.release()); macro.push (modifyCell.release()); + if (modifyDataRefNum.get()) + macro.push (modifyDataRefNum.release()); } else mDocument.getUndoStack().push (modifyData.release()); diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 026625795..0a3a950e0 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -670,27 +670,40 @@ void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) if (mOverrideFlags & Override_Position) { - for (int i=0; i<3; ++i) + //Do cell check first so positions can be compared + const CSMWorld::CellRef& ref = collection.getRecord(recordIndex).get(); + + if (CSMWorld::CellCoordinates::isExteriorCell(ref.mCell)) { - int column = collection.findColumnIndex (static_cast ( - CSMWorld::Columns::ColumnId_PositionXPos+i)); + // Find cell index at new position + std::pair cellIndex = CSMWorld::CellCoordinates::coordinatesToCellIndex( + mPositionOverride.pos[0], mPositionOverride.pos[1]); + std::pair originalIndex = ref.getCellIndex(); - commands.push (new CSMWorld::ModifyCommand (*model, - model->index (recordIndex, column), mPositionOverride.pos[i])); - } + int cellColumn = collection.findColumnIndex (static_cast ( + CSMWorld::Columns::ColumnId_Cell)); + int refNumColumn = collection.findColumnIndex (static_cast ( + CSMWorld::Columns::ColumnId_RefNum)); + + if (cellIndex != originalIndex) + { + /// \todo figure out worldspace (not important until multiple worldspaces are supported) + std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId (""); - int column = collection.findColumnIndex (static_cast ( - CSMWorld::Columns::ColumnId_Cell)); + commands.push (new CSMWorld::ModifyCommand (*model, + model->index (recordIndex, cellColumn), QString::fromUtf8 (cellId.c_str()))); + commands.push (new CSMWorld::ModifyCommand( *model, + model->index (recordIndex, refNumColumn), 0)); + } + } - if (CSMWorld::CellCoordinates::isExteriorCell(collection.getRecord (recordIndex).get().mCell)) + for (int i=0; i<3; ++i) { - std::pair cellIndex = collection.getRecord (recordIndex).get().getCellIndex(); - - /// \todo figure out worldspace (not important until multiple worldspaces are supported) - std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId (""); + int column = collection.findColumnIndex (static_cast ( + CSMWorld::Columns::ColumnId_PositionXPos+i)); commands.push (new CSMWorld::ModifyCommand (*model, - model->index (recordIndex, column), QString::fromUtf8 (cellId.c_str()))); + model->index (recordIndex, column), mPositionOverride.pos[i])); } } From c9df63ffd1c86eaf3dd327f2566cbf4f22f50b9a Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 20 Jan 2019 22:59:25 +0300 Subject: [PATCH 437/441] Replace BSAOpt hash calculation with a custom function --- CMakeLists.txt | 1 - components/bsa/compressedbsafile.cpp | 48 ++++++++---- components/bsa/compressedbsafile.hpp | 6 +- extern/BSAOpt/CMakeLists.txt | 17 ----- extern/BSAOpt/hash.cpp | 109 --------------------------- extern/BSAOpt/hash.hpp | 42 ----------- 6 files changed, 36 insertions(+), 187 deletions(-) delete mode 100644 extern/BSAOpt/CMakeLists.txt delete mode 100644 extern/BSAOpt/hash.cpp delete mode 100644 extern/BSAOpt/hash.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 57f6b8a5b..aaf269eef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -610,7 +610,6 @@ add_subdirectory (extern/oics) if (BUILD_OPENCS) add_subdirectory (extern/osgQt) endif() -add_subdirectory (extern/BSAOpt) # Components add_subdirectory (components) diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index 40dbc2856..ba96ee8de 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -28,12 +28,9 @@ #include #include -#include #include #include -#include // see: http://en.uesp.net/wiki/Tes4Mod:Hash_Calculation - #include #include #include @@ -197,8 +194,7 @@ void CompressedBSAFile::readHeader() if ((archiveFlags & 0x1) != 0) getBZString(folder, input); - std::string emptyString; - folderHash = GenOBHash(folder, emptyString); + folderHash = generateHash(folder, std::string()); std::map::iterator iter = mFolders.find(folderHash); if (iter == mFolders.end()) @@ -297,20 +293,13 @@ CompressedBSAFile::FileRecord CompressedBSAFile::getFileRecord(const std::string p.remove_filename(); std::string folder = p.string(); - // GenOBHash already converts to lowercase and replaces file separators but not for path - boost::algorithm::to_lower(folder); - std::replace(folder.begin(), folder.end(), '/', '\\'); - - std::string emptyString; - std::uint64_t folderHash = GenOBHash(folder, emptyString); + std::uint64_t folderHash = generateHash(folder, std::string()); std::map::const_iterator it = mFolders.find(folderHash); if (it == mFolders.end()) return FileRecord(); // folder not found, return default which has offset of sInvalidOffset - boost::algorithm::to_lower(stem); - boost::algorithm::to_lower(ext); - std::uint64_t fileHash = GenOBHashPair(stem, ext); + std::uint64_t fileHash = generateHash(stem, ext); std::map::const_iterator iter = it->second.files.find(fileHash); if (iter == it->second.files.end()) return FileRecord(); // file not found, return default which has offset of sInvalidOffset @@ -430,4 +419,35 @@ void CompressedBSAFile::convertCompressedSizesToUncompressed() } } +std::uint64_t CompressedBSAFile::generateHash(std::string stem, std::string extension) const +{ + size_t len = stem.length(); + if (len == 0) return 0; + std::uint64_t hash = 0; + unsigned int hash2 = 0; + Misc::StringUtils::lowerCaseInPlace(stem); + if (extension.empty()) // It's a folder. + std::replace(stem.begin(), stem.end(), '/', '\\'); + else + { + Misc::StringUtils::lowerCaseInPlace(extension); + for (const char &c : extension) + hash = hash * 0x1003f + c; + } + for (size_t i = 1; i < len-2 && len > 3; i++) + hash2 = hash2 * 0x1003f + stem[i]; + hash = (hash + hash2) << 32; + hash2 = (stem[0] << 24) | (len << 16); + if (len >= 3) hash2 |= stem[len-2] << 8; + if (len >= 2) hash2 |= stem[len-1]; + if (!extension.empty()) + { + if (extension == ".kf") hash2 |= 0x80; + else if (extension == ".nif") hash2 |= 0x8000; + else if (extension == ".dds") hash2 |= 0x8080; + else if (extension == ".wav") hash2 |= 0x80000000; + } + return hash + hash2; +} + } //namespace Bsa diff --git a/components/bsa/compressedbsafile.hpp b/components/bsa/compressedbsafile.hpp index b607eb3ef..a22d6e149 100644 --- a/components/bsa/compressedbsafile.hpp +++ b/components/bsa/compressedbsafile.hpp @@ -26,10 +26,6 @@ #ifndef BSA_COMPRESSED_BSA_FILE_H #define BSA_COMPRESSED_BSA_FILE_H -#include -#include -#include -#include #include namespace Bsa @@ -81,6 +77,8 @@ namespace Bsa void getBZString(std::string& str, std::istream& filestream); //mFiles used by OpenMW will contain uncompressed file sizes void convertCompressedSizesToUncompressed(); + /// \brief Normalizes given filename or folder and generates format-compatible hash. See https://en.uesp.net/wiki/Tes4Mod:Hash_Calculation. + std::uint64_t generateHash(std::string stem, std::string extension) const; Files::IStreamPtr getFile(const FileRecord& fileRecord); public: CompressedBSAFile(); diff --git a/extern/BSAOpt/CMakeLists.txt b/extern/BSAOpt/CMakeLists.txt deleted file mode 100644 index a7f1dcf74..000000000 --- a/extern/BSAOpt/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -# This is NOT intended as a stand-alone build system! Instead, you should include this from the main CMakeLists of your project. - -set(BSAOPTHASH_LIBRARY "bsaopthash") - -# Sources -set(SOURCE_FILES - hash.cpp -) - -add_library(${BSAOPTHASH_LIBRARY} STATIC ${SOURCE_FILES}) - -set(BSAOPTHASH_LIBRARIES ${BSAOPTHASH_LIBRARY}) - -link_directories(${CMAKE_CURRENT_BINARY_DIR}) -set(BSAOPTHASH_LIBRARIES ${BSAOPTHASH_LIBRARIES} PARENT_SCOPE) diff --git a/extern/BSAOpt/hash.cpp b/extern/BSAOpt/hash.cpp deleted file mode 100644 index 08c807da4..000000000 --- a/extern/BSAOpt/hash.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* Version: MPL 1.1/LGPL 3.0 - * - * "The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" - * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the - * License for the specific language governing rights and limitations - * under the License. - * - * The Original Code is BSAopt. - * - * The Initial Developer of the Original Code is - * Ethatron . Portions created by The Initial - * Developer are Copyright (C) 2011 The Initial Developer. - * All Rights Reserved. - * - * Alternatively, the contents of this file may be used under the terms - * of the GNU Library General Public License Version 3 license (the - * "LGPL License"), in which case the provisions of LGPL License are - * applicable instead of those above. If you wish to allow use of your - * version of this file only under the terms of the LGPL License and not - * to allow others to use your version of this file under the MPL, - * indicate your decision by deleting the provisions above and replace - * them with the notice and other provisions required by the LGPL License. - * If you do not delete the provisions above, a recipient may use your - * version of this file under either the MPL or the LGPL License." - */ - -#include -#include -#include - -#include "hash.hpp" - -std::uint32_t GenOBHashStr(const std::string& s) { - std::uint32_t hash = 0; - - for (std::size_t i = 0; i < s.length(); i++) { - hash *= 0x1003F; - hash += (unsigned char)s[i]; - } - - return hash; -} - -std::uint64_t GenOBHashPair(const std::string& fle, const std::string& ext) { - std::uint64_t hash = 0; - - if (fle.length() > 0) { - hash = (std::uint64_t)( - (((unsigned char)fle[fle.length() - 1]) * 0x1) + - ((fle.length() > 2 ? (unsigned char)fle[fle.length() - 2] : (unsigned char)0) * 0x100) + - (fle.length() * 0x10000) + - (((unsigned char)fle[0]) * 0x1000000) - ); - - if (fle.length() > 3) { - hash += (std::uint64_t)(GenOBHashStr(fle.substr(1, fle.length() - 3)) * 0x100000000); - } - } - - if (ext.length() > 0) { - hash += (std::uint64_t)(GenOBHashStr(ext) * 0x100000000LL); - - unsigned char i = 0; - if (ext == ".nif") i = 1; - if (ext == ".kf" ) i = 2; - if (ext == ".dds") i = 3; - if (ext == ".wav") i = 4; - - if (i != 0) { - unsigned char a = (unsigned char)(((i & 0xfc ) << 5) + (unsigned char)((hash & 0xff000000) >> 24)); - unsigned char b = (unsigned char)(((i & 0xfe ) << 6) + (unsigned char)( hash & 0x000000ff) ); - unsigned char c = (unsigned char)(( i << 7) + (unsigned char)((hash & 0x0000ff00) >> 8)); - - hash -= hash & 0xFF00FFFF; - hash += (std::uint32_t)((a << 24) + b + (c << 8)); - } - } - - return hash; -} - -std::uint64_t GenOBHash(const std::string& path, std::string& file) { - std::transform(file.begin(), file.end(), file.begin(), ::tolower); - std::replace(file.begin(), file.end(), '/', '\\'); - - std::string fle; - std::string ext; - - const char *_fle = file.data(); - const char *_ext = strrchr(_fle, '.'); - if (_ext) { - ext = file.substr((0 + _ext) - _fle); - fle = file.substr(0, ( _ext) - _fle); - } - else { - ext = ""; - fle = file; - } - - if (path.length() && fle.length()) - return GenOBHashPair(path + "\\" + fle, ext); - else - return GenOBHashPair(path + fle, ext); -} diff --git a/extern/BSAOpt/hash.hpp b/extern/BSAOpt/hash.hpp deleted file mode 100644 index cd936530a..000000000 --- a/extern/BSAOpt/hash.hpp +++ /dev/null @@ -1,42 +0,0 @@ -/* Version: MPL 1.1/LGPL 3.0 - * - * "The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" - * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the - * License for the specific language governing rights and limitations - * under the License. - * - * The Original Code is BSAopt. - * - * The Initial Developer of the Original Code is - * Ethatron . Portions created by The Initial - * Developer are Copyright (C) 2011 The Initial Developer. - * All Rights Reserved. - * - * Alternatively, the contents of this file may be used under the terms - * of the GNU Library General Public License Version 3 license (the - * "LGPL License"), in which case the provisions of LGPL License are - * applicable instead of those above. If you wish to allow use of your - * version of this file only under the terms of the LGPL License and not - * to allow others to use your version of this file under the MPL, - * indicate your decision by deleting the provisions above and replace - * them with the notice and other provisions required by the LGPL License. - * If you do not delete the provisions above, a recipient may use your - * version of this file under either the MPL or the LGPL License." - */ -#ifndef BSAOPT_HASH_H -#define BSAOPT_HASH_H - -#include - -std::uint32_t GenOBHashStr(const std::string& s); - -std::uint64_t GenOBHashPair(const std::string& fle, const std::string& ext); - -std::uint64_t GenOBHash(const std::string& path, std::string& file); - -#endif // BSAOPT_HASH_H From 6d8215ecb69f8efd57eefd06c02bc59fe957b4d9 Mon Sep 17 00:00:00 2001 From: Azdul Date: Sun, 20 Jan 2019 14:36:48 +0100 Subject: [PATCH 438/441] Google Test / Google Mock / OpenMW unit tests on Windows --- CI/before_script.msvc.sh | 63 ++++++++++++++++++++ apps/openmw_test_suite/CMakeLists.txt | 9 +++ apps/openmw_test_suite/openmw_test_suite.cpp | 6 ++ 3 files changed, 78 insertions(+) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 4fd352f7d..b5bf0b632 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -34,6 +34,8 @@ VS_VERSION="" NMAKE="" PLATFORM="" CONFIGURATION="" +TEST_FRAMEWORK="" +GOOGLE_INSTALL_ROOT="" while [ $# -gt 0 ]; do ARGSTR=$1 @@ -78,6 +80,9 @@ while [ $# -gt 0 ]; do CONFIGURATION=$1 shift ;; + t ) + TEST_FRAMEWORK=true ;; + h ) cat < Set the build platform, can also be set with environment variable PLATFORM. + -t + Build unit tests / Google test -u Configure for unity builds. -v <2013/2015/2017> @@ -395,6 +402,16 @@ if [ -z $SKIP_DOWNLOAD ]; then download "SDL 2.0.7" \ "https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \ "SDL2-2.0.7.zip" + + # Google test and mock + if [ ! -z $TEST_FRAMEWORK ]; then + echo "Google test 1.8.1..." + if [ -d googletest ]; then + printf " Google test exists, skipping." + else + git clone -b release-1.8.1 https://github.com/google/googletest.git + fi + fi fi cd .. #/.. @@ -670,6 +687,52 @@ printf "SDL 2.0.7... " add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll" echo Done. } +cd $DEPS +echo +# Google Test and Google Mock +if [ ! -z $TEST_FRAMEWORK ]; then + printf "Google test 1.8.1 ..." + + cd googletest + if [ ! -d build ]; then + mkdir build + fi + + cd build + + GOOGLE_INSTALL_ROOT="${DEPS_INSTALL}/GoogleTest" + if [ $CONFIGURATION == "Debug" ]; then + DEBUG_SUFFIX="d" + else + DEBUG_SUFFIX="" + fi + + if [ ! -d $GOOGLE_INSTALL_ROOT ]; then + + cmake .. -DCMAKE_BUILD_TYPE="${CONFIGURATION}" -DCMAKE_INSTALL_PREFIX="${GOOGLE_INSTALL_ROOT}" -DCMAKE_USE_WIN32_THREADS_INIT=1 -G "${GENERATOR}" -DBUILD_SHARED_LIBS=1 + cmake --build . --config "${CONFIGURATION}" + cmake --build . --target install --config "${CONFIGURATION}" + + add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gtest_main${DEBUG_SUFFIX}.dll" + add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gtest${DEBUG_SUFFIX}.dll" + add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gmock_main${DEBUG_SUFFIX}.dll" + add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gmock${DEBUG_SUFFIX}.dll" + fi + + add_cmake_opts -DBUILD_UNITTESTS=yes + # FindGTest and FindGMock do not work perfectly on Windows + # but we can help them by telling them everything we know about installation + add_cmake_opts -DGMOCK_ROOT="$GOOGLE_INSTALL_ROOT" + add_cmake_opts -DGTEST_ROOT="$GOOGLE_INSTALL_ROOT" + add_cmake_opts -DGTEST_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest${DEBUG_SUFFIX}.lib" + add_cmake_opts -DGTEST_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest_main${DEBUG_SUFFIX}.lib" + add_cmake_opts -DGMOCK_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock${DEBUG_SUFFIX}.lib" + add_cmake_opts -DGMOCK_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock_main${DEBUG_SUFFIX}.lib" + add_cmake_opts -DGTEST_LINKED_AS_SHARED_LIBRARY=True + echo Done. + +fi + echo cd $DEPS_INSTALL/.. echo diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 7f10ab2fb..e8b30815b 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -40,4 +40,13 @@ if (GTEST_FOUND AND GMOCK_FOUND) add_definitions(--coverage) target_link_libraries(openmw_test_suite gcov) endif() + + if (MSVC) + if (CMAKE_CL_64) + # Debug version of openmw_unit_tests needs increased number of sections beyond 2^16 + # just like openmw and openmw-cs + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") + endif (CMAKE_CL_64) + endif (MSVC) + endif() diff --git a/apps/openmw_test_suite/openmw_test_suite.cpp b/apps/openmw_test_suite/openmw_test_suite.cpp index 7cc76b25b..7364b20fd 100644 --- a/apps/openmw_test_suite/openmw_test_suite.cpp +++ b/apps/openmw_test_suite/openmw_test_suite.cpp @@ -1,6 +1,12 @@ #include +#ifdef WIN32 +//we cannot use GTEST_API_ before main if we're building standalone exe application, +//and we're linking GoogleTest / GoogleMock as DLLs and not linking gtest_main / gmock_main +int main(int argc, char **argv) { +#else GTEST_API_ int main(int argc, char **argv) { +#endif testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } From 32013d35f213b0b13577a1379f89243345b83c34 Mon Sep 17 00:00:00 2001 From: Azdul Date: Mon, 21 Jan 2019 20:58:24 +0100 Subject: [PATCH 439/441] Formatting of CMakeLists made consistent with other CMakeLists files. --- apps/openmw_test_suite/CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index e8b30815b..907052882 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -42,11 +42,11 @@ if (GTEST_FOUND AND GMOCK_FOUND) endif() if (MSVC) - if (CMAKE_CL_64) - # Debug version of openmw_unit_tests needs increased number of sections beyond 2^16 - # just like openmw and openmw-cs - set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") - endif (CMAKE_CL_64) + if (CMAKE_CL_64) + # Debug version of openmw_unit_tests needs increased number of sections beyond 2^16 + # just like openmw and openmw-cs + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") + endif (CMAKE_CL_64) endif (MSVC) endif() From 2c38e337ae78d620ec1af72f11b29efb59f6eb0d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 23 Jan 2019 21:01:54 +0400 Subject: [PATCH 440/441] Support NiSwitchNode (feature #4812) --- CHANGELOG.md | 1 + components/nifosg/nifloader.cpp | 25 +++++++++++++++++++++++++ components/sceneutil/optimizer.cpp | 13 +++++++++++++ components/sceneutil/optimizer.hpp | 2 ++ 4 files changed, 41 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fda600fc..31e086842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Feature #3610: Option to invert X axis Feature #4673: Weapon sheathing Feature #4730: Native animated containers support + Feature #4812: Support NiSwitchNode Task #4686: Upgrade media decoder to a more current FFmpeg API 0.45.0 diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 08f6eb96c..0ed485862 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -335,6 +336,13 @@ namespace NifOsg return lod; } + osg::ref_ptr handleSwitchNode(const Nif::NiSwitchNode* niSwitchNode) + { + osg::ref_ptr switchNode (new osg::Switch); + switchNode->setNewChildDefaultValue(false); + return switchNode; + } + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) { if (!st) @@ -593,6 +601,23 @@ namespace NifOsg node = lod; } + if (nifNode->recType == Nif::RC_NiSwitchNode) + { + const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); + osg::ref_ptr switchNode = handleSwitchNode(niSwitchNode); + node->addChild(switchNode); + const Nif::NodeList &children = niSwitchNode->children; + for(size_t i = 0;i < children.length();++i) + { + if(!children[i].empty()) + handleNode(children[i].getPtr(), switchNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode); + } + + // show only first child by default + switchNode->setSingleChildOn(0); + return switchNode; + } + const Nif::NiNode *ninode = dynamic_cast(nifNode); if(ninode) { diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 5d50c369d..f2717fea2 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -807,6 +807,13 @@ void Optimizer::RemoveRedundantNodesVisitor::apply(osg::LOD& lod) traverse(*lod.getChild(i)); } +void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Switch& switchNode) +{ + // We should keep all switch child nodes since they reflect different switch states. + for (unsigned int i=0; i Date: Thu, 24 Jan 2019 13:33:18 +0000 Subject: [PATCH 441/441] Gmst typo in MWMechanics::NpcStats::increaseSkill --- apps/openmw/mwmechanics/npcstats.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 47d886e3f..bdd4e6968 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -152,20 +152,18 @@ float MWMechanics::NpcStats::getSkillProgressRequirement (int skillIndex, const float typeFactor = gmst.find ("fMiscSkillBonus")->mValue.getFloat(); for (int i=0; i<5; ++i) + { if (class_.mData.mSkills[i][0]==skillIndex) { typeFactor = gmst.find ("fMinorSkillBonus")->mValue.getFloat(); - break; } - - for (int i=0; i<5; ++i) - if (class_.mData.mSkills[i][1]==skillIndex) + else if (class_.mData.mSkills[i][1]==skillIndex) { typeFactor = gmst.find ("fMajorSkillBonus")->mValue.getFloat(); - break; } + } progressRequirement *= typeFactor; @@ -233,15 +231,14 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas if (class_.mData.mSkills[k][0] == skillIndex) { mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger(); - increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger(); + increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger(); + break; } - } - for (int k=0; k<5; ++k) - { - if (class_.mData.mSkills[k][1] == skillIndex) + else if (class_.mData.mSkills[k][1] == skillIndex) { mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger(); - increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger(); + increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger(); + break; } }