Merge remote-tracking branch 'scrawl/essimporter'

This commit is contained in:
Marc Zinnschlag 2015-01-20 15:53:48 +01:00
commit a266dffb4b
55 changed files with 1978 additions and 89 deletions

View file

@ -0,0 +1,29 @@
set(ESSIMPORTER_FILES
main.cpp
importer.cpp
importplayer.cpp
importnpcc.cpp
importcrec.cpp
importcellref.cpp
importacdt.cpp
importinventory.cpp
importklst.cpp
importercontext.cpp
converter.cpp
convertacdt.cpp
convertnpcc.cpp
)
add_executable(openmw-essimporter
${ESSIMPORTER_FILES}
)
target_link_libraries(openmw-essimporter
${Boost_LIBRARIES}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(openmw-essimporter gcov)
endif()

View file

@ -0,0 +1,42 @@
#include "convertacdt.hpp"
namespace ESSImport
{
int translateDynamicIndex(int mwIndex)
{
if (mwIndex == 1)
return 2;
else if (mwIndex == 2)
return 1;
return mwIndex;
}
void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats)
{
for (int i=0; i<3; ++i)
{
int writeIndex = translateDynamicIndex(i);
cStats.mDynamic[writeIndex].mBase = acdt.mDynamic[i][1];
cStats.mDynamic[writeIndex].mMod = acdt.mDynamic[i][1];
cStats.mDynamic[writeIndex].mCurrent = acdt.mDynamic[i][0];
}
for (int i=0; i<8; ++i)
{
cStats.mAttributes[i].mBase = acdt.mAttributes[i][1];
cStats.mAttributes[i].mMod = acdt.mAttributes[i][0];
cStats.mAttributes[i].mCurrent = acdt.mAttributes[i][0];
}
}
void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats)
{
for (int i=0; i<ESM::Skill::Length; ++i)
{
npcStats.mSkills[i].mRegular.mMod = actorData.mSkills[i][1];
npcStats.mSkills[i].mRegular.mCurrent = actorData.mSkills[i][1];
npcStats.mSkills[i].mRegular.mBase = actorData.mSkills[i][0];
}
}
}

View file

@ -0,0 +1,22 @@
#ifndef OPENMW_ESSIMPORT_CONVERTACDT_H
#define OPENMW_ESSIMPORT_CONVERTACDT_H
#include <components/esm/creaturestats.hpp>
#include <components/esm/npcstats.hpp>
#include <components/esm/loadskil.hpp>
#include "importacdt.hpp"
namespace ESSImport
{
// OpenMW uses Health,Magicka,Fatigue, MW uses Health,Fatigue,Magicka
int translateDynamicIndex(int mwIndex);
void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats);
void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats);
}
#endif

View file

@ -0,0 +1,241 @@
#include "converter.hpp"
#include <OgreImage.h>
#include <components/esm/creaturestate.hpp>
namespace
{
void convertImage(char* data, int size, int width, int height, Ogre::PixelFormat pf, const std::string& out)
{
Ogre::Image screenshot;
Ogre::DataStreamPtr stream (new Ogre::MemoryDataStream(data, size));
screenshot.loadRawData(stream, width, height, 1, pf);
screenshot.save(out);
}
}
namespace ESSImport
{
struct MAPH
{
unsigned int size;
unsigned int value;
};
void ConvertFMAP::read(ESM::ESMReader &esm)
{
MAPH maph;
esm.getHNT(maph, "MAPH");
std::vector<char> data;
esm.getSubNameIs("MAPD");
esm.getSubHeader();
data.resize(esm.getSubSize());
esm.getExact(&data[0], data.size());
convertImage(&data[0], data.size(), maph.size, maph.size, Ogre::PF_BYTE_RGB, "map.tga");
}
void ConvertCell::read(ESM::ESMReader &esm)
{
ESM::Cell cell;
std::string id = esm.getHNString("NAME");
cell.mName = id;
cell.load(esm, false);
// note if the player is in a nameless exterior cell, we will assign the cellId later based on player position
if (id == mContext->mPlayerCellName)
{
mContext->mPlayer.mCellId = cell.getCellId();
}
Cell newcell;
newcell.mCell = cell;
// fog of war
// seems to be a 1-bit pixel format, 16*16 pixels
// TODO: add bleeding of FOW into neighbouring cells (openmw handles this by writing to the textures,
// MW handles it when rendering only)
unsigned char nam8[32];
// exterior has 1 NAM8, interior can have multiple ones, and have an extra 4 byte flag at the start
// (probably offset of that specific fog texture?)
while (esm.isNextSub("NAM8"))
{
esm.getSubHeader();
if (esm.getSubSize() == 36)
{
// flag on interiors
esm.skip(4);
}
esm.getExact(nam8, 32);
newcell.mFogOfWar.reserve(16*16);
for (int x=0; x<16; ++x)
{
for (int y=0; y<16; ++y)
{
size_t pos = x*16+y;
size_t bytepos = pos/8;
assert(bytepos<32);
int bit = pos%8;
newcell.mFogOfWar.push_back(((nam8[bytepos] >> bit) & (0x1)) ? 0xffffffff : 0x000000ff);
}
}
if (cell.isExterior())
{
std::ostringstream filename;
filename << "fog_" << cell.mData.mX << "_" << cell.mData.mY << ".tga";
convertImage((char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size()*4, 16, 16, Ogre::PF_BYTE_RGBA, filename.str());
}
}
// moved reference, not handled yet
// NOTE: MVRF can also occur in within normal references (importcellref.cpp)?
// this does not match the ESM file implementation,
// verify if that can happen with ESM files too
while (esm.isNextSub("MVRF"))
{
esm.skipHSub(); // skip MVRF
esm.getSubName();
esm.skipHSub(); // skip CNDT
}
std::vector<CellRef> cellrefs;
while (esm.hasMoreSubs() && esm.isNextSub("FRMR"))
{
CellRef ref;
ref.load (esm);
if (esm.isNextSub("DELE"))
{
// strangely this can be e.g. 52 instead of just 1,
std::cout << "deleted ref " << ref.mIndexedRefId << std::endl;
esm.skipHSub();
}
cellrefs.push_back(ref);
}
while (esm.isNextSub("MPCD"))
{
float notepos[3];
esm.getHT(notepos, 3*sizeof(float));
// Markers seem to be arranged in a 32*32 grid, notepos has grid-indices.
// This seems to be the reason markers can't be placed everywhere in interior cells,
// i.e. when the grid is exceeded.
// Converting the interior markers correctly could be rather tricky, but is probably similar logic
// as used for the FoW texture placement, which we need to figure out anyway
notepos[1] += 31.f;
notepos[0] += 0.5;
notepos[1] += 0.5;
notepos[0] = 8192 * notepos[0] / 32.f;
notepos[1] = 8192 * notepos[1] / 32.f;
if (cell.isExterior())
{
notepos[0] += 8192 * cell.mData.mX;
notepos[1] += 8192 * cell.mData.mY;
}
// TODO: what encoding is this in?
std::string note = esm.getHNString("MPNT");
ESM::CustomMarker marker;
marker.mWorldX = notepos[0];
marker.mWorldY = notepos[1];
marker.mNote = note;
marker.mCell = cell.getCellId();
mMarkers.push_back(marker);
}
newcell.mRefs = cellrefs;
// FIXME: map by ID for exterior cells
mCells[id] = newcell;
}
void ConvertCell::write(ESM::ESMWriter &esm)
{
for (std::map<std::string, Cell>::const_iterator it = mCells.begin(); it != mCells.end(); ++it)
{
const ESM::Cell& cell = it->second.mCell;
esm.startRecord(ESM::REC_CSTA);
ESM::CellState csta;
csta.mHasFogOfWar = 0;
csta.mId = cell.getCellId();
csta.mId.save(esm);
// TODO csta.mLastRespawn;
// shouldn't be needed if we respawn on global schedule like in original MW
csta.mWaterLevel = cell.mWater;
csta.save(esm);
for (std::vector<CellRef>::const_iterator refIt = it->second.mRefs.begin(); refIt != it->second.mRefs.end(); ++refIt)
{
const CellRef& cellref = *refIt;
ESM::CellRef out;
out.blank();
if (cellref.mIndexedRefId.size() < 8)
{
std::cerr << "CellRef with no index?" << std::endl;
continue;
}
std::stringstream stream;
stream << cellref.mIndexedRefId.substr(cellref.mIndexedRefId.size()-8,8);
int refIndex;
stream >> refIndex;
out.mRefID = cellref.mIndexedRefId.substr(0,cellref.mIndexedRefId.size()-8);
std::map<std::pair<int, std::string>, CREC>::const_iterator crecIt = mContext->mCreatureChanges.find(
std::make_pair(refIndex, out.mRefID));
if (crecIt != mContext->mCreatureChanges.end())
{
ESM::CreatureState objstate;
objstate.blank();
convertACDT(cellref.mActorData.mACDT, objstate.mCreatureStats);
objstate.mEnabled = cellref.mEnabled;
objstate.mPosition = cellref.mPos;
objstate.mRef = out;
objstate.mRef.mRefNum = cellref.mRefNum;
// FIXME: change save format to not require object type, instead look up it up using the RefId
esm.writeHNT ("OBJE", ESM::REC_CREA);
objstate.save(esm);
continue;
}
std::map<std::pair<int, std::string>, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find(
std::make_pair(refIndex, out.mRefID));
if (npccIt != mContext->mNpcChanges.end())
{
ESM::NpcState objstate;
objstate.blank();
convertACDT(cellref.mActorData.mACDT, objstate.mCreatureStats);
convertNpcData(cellref.mActorData, objstate.mNpcStats);
objstate.mEnabled = cellref.mEnabled;
objstate.mPosition = cellref.mPos;
objstate.mRef = out;
objstate.mRef.mRefNum = cellref.mRefNum;
esm.writeHNT ("OBJE", ESM::REC_NPC_);
objstate.save(esm);
continue;
}
std::cerr << "Can't find type for " << refIndex << " " << out.mRefID << std::endl;
}
esm.endRecord(ESM::REC_CSTA);
}
for (std::vector<ESM::CustomMarker>::const_iterator it = mMarkers.begin(); it != mMarkers.end(); ++it)
{
esm.startRecord(ESM::REC_MARK);
it->save(esm);
esm.endRecord(ESM::REC_MARK);
}
}
}

View file

@ -0,0 +1,279 @@
#ifndef OPENMW_ESSIMPORT_CONVERTER_H
#define OPENMW_ESSIMPORT_CONVERTER_H
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/loadcell.hpp>
#include <components/esm/loadbook.hpp>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadglob.hpp>
#include <components/esm/cellstate.hpp>
#include <components/esm/custommarkerstate.hpp>
#include "importcrec.hpp"
#include "importercontext.hpp"
#include "importcellref.hpp"
#include "importklst.hpp"
#include "convertacdt.hpp"
#include "convertnpcc.hpp"
namespace ESSImport
{
class Converter
{
public:
/// @return the order for writing this converter's records to the output file, in relation to other converters
virtual int getStage() { return 1; }
virtual ~Converter() {}
void setContext(Context& context) { mContext = &context; }
virtual void read(ESM::ESMReader& esm)
{
}
/// Called after the input file has been read in completely, which may be necessary
/// if the conversion process relies on information in other records
virtual void write(ESM::ESMWriter& esm)
{
}
protected:
Context* mContext;
};
/// Default converter: simply reads the record and writes it unmodified to the output
template <typename T>
class DefaultConverter : public Converter
{
public:
virtual int getStage() { return 0; }
virtual void read(ESM::ESMReader& esm)
{
std::string id = esm.getHNString("NAME");
T record;
record.load(esm);
mRecords[id] = record;
}
virtual void write(ESM::ESMWriter& esm)
{
for (typename std::map<std::string, T>::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);
}
}
protected:
std::map<std::string, T> mRecords;
};
class ConvertNPC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
// this is always the player
ESM::NPC npc;
std::string id = esm.getHNString("NAME");
npc.load(esm);
if (id != "player") // seems to occur sometimes, with "chargen X" names
std::cerr << "non-player NPC record: " << id << std::endl;
else
{
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel;
mContext->mPlayerBase = npc;
std::map<const int, float> empty;
// FIXME: not working?
for (std::vector<std::string>::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it)
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[*it] = empty;
}
}
};
class ConvertGlobal : public DefaultConverter<ESM::Global>
{
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"))
mContext->mHour = global.mValue.getFloat();
if (Misc::StringUtils::ciEqual(id, "day"))
mContext->mDay = global.mValue.getInteger();
if (Misc::StringUtils::ciEqual(id, "month"))
mContext->mMonth = global.mValue.getInteger();
if (Misc::StringUtils::ciEqual(id, "year"))
mContext->mYear = global.mValue.getInteger();
mRecords[id] = global;
}
};
class ConvertClass : public DefaultConverter<ESM::Class>
{
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
ESM::Class class_;
class_.load(esm);
if (id == "NEWCLASSID_CHARGEN")
mContext->mCustomPlayerClassName = class_.mName;
mRecords[id] = class_;
}
};
class ConvertBook : public DefaultConverter<ESM::Book>
{
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));
mRecords[id] = book;
}
};
class ConvertNPCC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
NPCC npcc;
npcc.load(esm);
if (id == "PlayerSaveGame")
{
convertNPCC(npcc, mContext->mPlayer.mObject);
}
else
mContext->mNpcChanges.insert(std::make_pair(std::make_pair(npcc.mIndex,id), npcc));
}
};
class ConvertREFR : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
REFR refr;
refr.load(esm);
assert(refr.mRefID == "PlayerSaveGame");
mContext->mPlayer.mObject.mPosition = refr.mPos;
ESM::CreatureStats& cStats = mContext->mPlayer.mObject.mCreatureStats;
convertACDT(refr.mActorData.mACDT, cStats);
ESM::NpcStats& npcStats = mContext->mPlayer.mObject.mNpcStats;
convertNpcData(refr.mActorData, npcStats);
}
};
class ConvertPCDT : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
PCDT pcdt;
pcdt.load(esm);
mContext->mPlayer.mBirthsign = pcdt.mBirthsign;
mContext->mPlayer.mObject.mNpcStats.mBounty = pcdt.mBounty;
for (std::vector<PCDT::FNAM>::const_iterator it = pcdt.mFactions.begin(); it != pcdt.mFactions.end(); ++it)
{
ESM::NpcStats::Faction faction;
faction.mExpelled = it->mFlags & 0x2;
faction.mRank = it->mRank;
faction.mReputation = it->mReputation;
mContext->mPlayer.mObject.mNpcStats.mFactions[it->mFactionName.toString()] = faction;
}
}
};
class ConvertCREC : public Converter
{
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
CREC crec;
crec.load(esm);
mContext->mCreatureChanges.insert(std::make_pair(std::make_pair(crec.mIndex,id), crec));
}
};
class ConvertFMAP : public Converter
{
public:
virtual void read(ESM::ESMReader &esm);
};
class ConvertCell : public Converter
{
public:
virtual void read(ESM::ESMReader& esm);
virtual void write(ESM::ESMWriter& esm);
private:
struct Cell
{
ESM::Cell mCell;
std::vector<CellRef> mRefs;
std::vector<unsigned int> mFogOfWar;
};
std::map<std::string, Cell> mCells;
std::vector<ESM::CustomMarker> mMarkers;
};
class ConvertKLST : public Converter
{
public:
virtual void read(ESM::ESMReader& esm)
{
KLST klst;
klst.load(esm);
mKillCounter = klst.mKillCounter;
mContext->mPlayer.mObject.mNpcStats.mWerewolfKills = klst.mWerewolfKills;
}
virtual void write(ESM::ESMWriter &esm)
{
esm.startRecord(ESM::REC_DCOU);
for (std::map<std::string, int>::const_iterator it = mKillCounter.begin(); it != mKillCounter.end(); ++it)
{
esm.writeHNString("ID__", it->first);
esm.writeHNT ("COUN", it->second);
}
esm.endRecord(ESM::REC_DCOU);
}
private:
std::map<std::string, int> mKillCounter;
};
}
#endif

View file

@ -0,0 +1,10 @@
#include "convertnpcc.hpp"
namespace ESSImport
{
void convertNPCC(const NPCC &npcc, ESM::NpcState &npcState)
{
npcState.mNpcStats.mReputation = npcc.mNPDT.mReputation;
}
}

View file

@ -0,0 +1,15 @@
#ifndef OPENMW_ESSIMPORT_CONVERTNPCC_H
#define OPENMW_ESSIMPORT_CONVERTNPCC_H
#include "importnpcc.hpp"
#include <components/esm/npcstate.hpp>
namespace ESSImport
{
void convertNPCC (const NPCC& npcc, ESM::NpcState& npcState);
}
#endif

View file

@ -0,0 +1,104 @@
#include "importacdt.hpp"
#include <components/esm/esmreader.hpp>
#include <components/esm/cellref.hpp>
namespace ESSImport
{
void ActorData::load(ESM::ESMReader &esm)
{
// unsure at which point between NAME and ESM::CellRef
if (esm.isNextSub("MNAM"))
esm.skipHSub();
if (esm.isNextSub("ACTN"))
esm.skipHSub();
if (esm.isNextSub("STPR"))
esm.skipHSub();
ESM::CellRef bla;
bla.ESM::CellRef::loadData(esm);
// FIXME: actually should be required for all actors?, but ActorData is currently in base CellRef
esm.getHNOT(mACDT, "ACDT");
ACSC acsc;
esm.getHNOT(acsc, "ACSC");
esm.getHNOT(acsc, "ACSL");
if (esm.isNextSub("CSTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
if (esm.isNextSub("LSTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
// unsure at which point between LSTN and TGTN
if (esm.isNextSub("CSHN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
// unsure if before or after CSTN/LSTN
if (esm.isNextSub("LSHN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
while (esm.isNextSub("TGTN"))
esm.skipHSub(); // "PlayerSaveGame", link to some object?
while (esm.isNextSub("FGTN"))
esm.getHString(); // fight target?
// unsure at which point between TGTN and CRED
if (esm.isNextSub("AADT"))
{
// occured when a creature was in the middle of its attack, 44 bytes
esm.skipHSub();
}
// unsure at which point between FGTN and CHRD
if (esm.isNextSub("PWPC"))
esm.skipHSub();
if (esm.isNextSub("PWPS"))
esm.skipHSub();
if (esm.isNextSub("WNAM"))
{
esm.skipHSub(); // seen values: "ancestor guardian", "bound dagger_en". Summoned creature / bound weapons?
if (esm.isNextSub("XNAM"))
{
// "demon tanto", probably the ID of spell/item that created the bound weapon/crature?
esm.skipHSub();
}
if (esm.isNextSub("YNAM"))
esm.skipHSub(); // 4 byte, 0
}
if (esm.isNextSub("CHRD")) // npc only
esm.getHExact(mSkills, 27*2*sizeof(int));
if (esm.isNextSub("CRED")) // creature only
esm.getHExact(mCombatStats, 3*2*sizeof(int));
mScript = esm.getHNOString("SCRI");
// script variables?
if (!mScript.empty())
{
if (esm.isNextSub("SLCS"))
esm.skipHSub();
if (esm.isNextSub("SLSD")) // Short Data?
esm.skipHSub();
if (esm.isNextSub("SLFD")) // Float Data?
esm.skipHSub();
}
if (esm.isNextSub("ND3D"))
esm.skipHSub();
if (esm.isNextSub("ANIS"))
esm.skipHSub();
}
}

View file

@ -0,0 +1,49 @@
#ifndef OPENMW_ESSIMPORT_ACDT_H
#define OPENMW_ESSIMPORT_ACDT_H
#include <string>
namespace ESM
{
struct ESMReader;
}
namespace ESSImport
{
/// Actor data, shared by (at least) REFR and CellRef
struct ACDT
{
unsigned char mUnknown1[40];
float mDynamic[3][2];
unsigned char mUnknown2[16];
float mAttributes[8][2];
unsigned char mUnknown3[120];
};
struct ActorData
{
ACDT mACDT;
int mSkills[27][2];
// creature combat stats, base and modified
// I think these can be ignored in the conversion, because it is not possible
// to change them ingame
int mCombatStats[3][2];
std::string mScript;
void load(ESM::ESMReader& esm);
};
/// Unknown, shared by (at least) REFR and CellRef
struct ACSC
{
unsigned char unknown[112];
};
}
#endif

View file

@ -0,0 +1,43 @@
#include "importcellref.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void CellRef::load(ESM::ESMReader &esm)
{
// (FRMR subrecord name is already read by the loop in ConvertCell)
esm.getHT(mRefNum.mIndex); // FRMR
// this is required since openmw supports more than 255 content files
int pluginIndex = (mRefNum.mIndex & 0xff000000) >> 24;
mRefNum.mContentFile = pluginIndex-1;
mRefNum.mIndex &= 0x00ffffff;
mIndexedRefId = esm.getHNString("NAME");
if (esm.isNextSub("LVCR"))
esm.skipHSub();
mActorData.load(esm);
mEnabled = true;
esm.getHNOT(mEnabled, "ZNAM");
// should occur for all references but not levelled creature spawners
esm.getHNOT(mPos, "DATA", 24);
// i've seen DATA record TWICE on a creature record - and with the exact same content too! weird
// alarmvoi0000.ess
esm.getHNOT(mPos, "DATA", 24);
if (esm.isNextSub("MVRF"))
{
esm.skipHSub();
esm.getSubName();
esm.skipHSub();
}
}
}

View file

@ -0,0 +1,37 @@
#ifndef OPENMW_ESSIMPORT_CELLREF_H
#define OPENMW_ESSIMPORT_CELLREF_H
#include <string>
#include <components/esm/cellref.hpp>
#include "importacdt.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
// Not sure if we can share any code with ESM::CellRef here
struct CellRef
{
std::string mIndexedRefId;
ESM::RefNum mRefNum;
ActorData mActorData;
ESM::Position mPos;
std::string mScript;
bool mEnabled;
void load(ESM::ESMReader& esm);
};
}
#endif

View file

@ -0,0 +1,20 @@
#include "importcrec.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void CREC::load(ESM::ESMReader &esm)
{
esm.getHNT(mIndex, "INDX");
// equivalent of ESM::Creature XSCL? probably don't have to convert this,
// since the value can't be changed
float scale;
esm.getHNOT(scale, "XSCL");
mInventory.load(esm);
}
}

View file

@ -0,0 +1,26 @@
#ifndef OPENMW_ESSIMPORT_CREC_H
#define OPENMW_ESSIMPORT_CREC_H
#include "importinventory.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// Creature changes
struct CREC
{
int mIndex;
Inventory mInventory;
void load(ESM::ESMReader& esm);
};
}
#endif

View file

@ -0,0 +1,327 @@
#include "importer.hpp"
#include <OgreRoot.h>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/defs.hpp>
#include <components/esm/savedgame.hpp>
#include <components/esm/player.hpp>
#include <components/esm/loadalch.hpp>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadspel.hpp>
#include <components/esm/loadarmo.hpp>
#include <components/esm/loadweap.hpp>
#include <components/esm/loadclot.hpp>
#include <components/esm/loadench.hpp>
#include <components/esm/loadweap.hpp>
#include <components/esm/loadlevlist.hpp>
#include <components/esm/loadglob.hpp>
#include "importercontext.hpp"
#include "converter.hpp"
namespace
{
void writeScreenshot(const ESM::Header& fileHeader, ESM::SavedGame& out)
{
Ogre::Image screenshot;
std::vector<unsigned char> screenshotData = fileHeader.mSCRS; // MemoryDataStream doesn't work with const data :(
Ogre::DataStreamPtr screenshotStream (new Ogre::MemoryDataStream(&screenshotData[0], screenshotData.size()));
screenshot.loadRawData(screenshotStream, 128, 128, 1, Ogre::PF_BYTE_BGRA);
Ogre::DataStreamPtr encoded = screenshot.encode("jpg");
out.mScreenshot.resize(encoded->size());
encoded->read(&out.mScreenshot[0], encoded->size());
}
}
namespace ESSImport
{
Importer::Importer(const std::string &essfile, const std::string &outfile)
: mEssFile(essfile)
, mOutFile(outfile)
{
}
struct File
{
struct Subrecord
{
std::string mName;
size_t mFileOffset;
std::vector<unsigned char> mData;
};
struct Record
{
std::string mName;
size_t mFileOffset;
std::vector<Subrecord> mSubrecords;
};
std::vector<Record> mRecords;
};
void read(const std::string& filename, File& file)
{
ESM::ESMReader esm;
esm.open(filename);
while (esm.hasMoreRecs())
{
ESM::NAME n = esm.getRecName();
esm.getRecHeader();
File::Record rec;
rec.mName = n.toString();
rec.mFileOffset = esm.getFileOffset();
while (esm.hasMoreSubs())
{
File::Subrecord sub;
esm.getSubName();
esm.getSubHeader();
sub.mFileOffset = esm.getFileOffset();
sub.mName = esm.retSubName().toString();
sub.mData.resize(esm.getSubSize());
esm.getExact(&sub.mData[0], sub.mData.size());
rec.mSubrecords.push_back(sub);
}
file.mRecords.push_back(rec);
}
}
void Importer::compare()
{
// data that always changes (and/or is already fully decoded) should be blacklisted
std::set<std::pair<std::string, std::string> > blacklist;
blacklist.insert(std::make_pair("GLOB", "FLTV")); // gamehour
blacklist.insert(std::make_pair("REFR", "DATA")); // player position
blacklist.insert(std::make_pair("CELL", "NAM8")); // fog of war
File file1;
read(mEssFile, file1);
File file2;
read(mOutFile, file2); // todo rename variable
// FIXME: use max(size1, size2)
for (unsigned int i=0; i<file1.mRecords.size(); ++i)
{
File::Record rec = file1.mRecords[i];
if (i >= file2.mRecords.size())
{
std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset;
return;
}
File::Record rec2 = file2.mRecords[i];
if (rec.mName != rec2.mName)
{
std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl;
return; // TODO: try to recover
}
// FIXME: use max(size1, size2)
for (unsigned int j=0; j<rec.mSubrecords.size(); ++j)
{
File::Subrecord sub = rec.mSubrecords[j];
if (j >= rec2.mSubrecords.size())
{
std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset;
return;
}
File::Subrecord sub2 = rec2.mSubrecords[j];
if (sub.mName != sub2.mName)
{
std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl;
break; // TODO: try to recover
}
if (sub.mData != sub2.mData)
{
if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end())
continue;
std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl;
std::cout << "Data 1:" << std::endl;
for (unsigned int k=0; k<sub.mData.size(); ++k)
{
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)sub.mData[k] << " ";
}
std::cout << std::endl;
std::cout << "Data 2:" << std::endl;
for (unsigned int k=0; k<sub2.mData.size(); ++k)
{
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)sub2.mData[k] << " ";
}
std::cout << std::endl;
}
}
}
}
void Importer::run()
{
// construct Ogre::Root to gain access to image codecs
Ogre::LogManager logman;
Ogre::Root root;
ESM::ESMReader esm;
esm.open(mEssFile);
Context context;
const ESM::Header& header = esm.getHeader();
context.mPlayerCellName = header.mGameData.mCurrentCell.toString();
const unsigned int recREFR = ESM::FourCC<'R','E','F','R'>::value;
const unsigned int recPCDT = ESM::FourCC<'P','C','D','T'>::value;
const unsigned int recFMAP = ESM::FourCC<'F','M','A','P'>::value;
const unsigned int recKLST = ESM::FourCC<'K','L','S','T'>::value;
std::map<unsigned int, boost::shared_ptr<Converter> > converters;
converters[ESM::REC_GLOB] = boost::shared_ptr<Converter>(new ConvertGlobal());
converters[ESM::REC_BOOK] = boost::shared_ptr<Converter>(new ConvertBook());
converters[ESM::REC_NPC_] = boost::shared_ptr<Converter>(new ConvertNPC());
converters[ESM::REC_NPCC] = boost::shared_ptr<Converter>(new ConvertNPCC());
converters[ESM::REC_CREC] = boost::shared_ptr<Converter>(new ConvertCREC());
converters[recREFR] = boost::shared_ptr<Converter>(new ConvertREFR());
converters[recPCDT] = boost::shared_ptr<Converter>(new ConvertPCDT());
converters[recFMAP] = boost::shared_ptr<Converter>(new ConvertFMAP());
converters[recKLST] = boost::shared_ptr<Converter>(new ConvertKLST());
converters[ESM::REC_CELL] = boost::shared_ptr<Converter>(new ConvertCell());
converters[ESM::REC_ALCH] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Potion>());
converters[ESM::REC_CLAS] = boost::shared_ptr<Converter>(new ConvertClass());
converters[ESM::REC_SPEL] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Spell>());
converters[ESM::REC_ARMO] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Armor>());
converters[ESM::REC_WEAP] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
converters[ESM::REC_CLOT] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Clothing>());
converters[ESM::REC_ENCH] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Enchantment>());
converters[ESM::REC_WEAP] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
converters[ESM::REC_LEVC] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::CreatureLevList>());
converters[ESM::REC_LEVI] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::ItemLevList>());
std::set<unsigned int> unknownRecords;
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
it != converters.end(); ++it)
{
it->second->setContext(context);
}
while (esm.hasMoreRecs())
{
ESM::NAME n = esm.getRecName();
esm.getRecHeader();
std::map<unsigned int, boost::shared_ptr<Converter> >::iterator it = converters.find(n.val);
if (it != converters.end())
{
it->second->read(esm);
}
else
{
if (unknownRecords.insert(n.val).second)
std::cerr << "unknown record " << n.toString() << std::endl;
esm.skipRecord();
}
}
ESM::ESMWriter writer;
writer.setFormat (ESM::Header::CurrentFormat);
std::ofstream stream(mOutFile.c_str(), std::ios::binary);
// all unused
writer.setVersion(0);
writer.setType(0);
writer.setAuthor("");
writer.setDescription("");
writer.setRecordCount (0);
for (std::vector<ESM::Header::MasterData>::const_iterator it = header.mMaster.begin();
it != header.mMaster.end(); ++it)
writer.addMaster (it->name, 0); // not using the size information anyway -> use value of 0
writer.save (stream);
ESM::SavedGame profile;
for (std::vector<ESM::Header::MasterData>::const_iterator it = header.mMaster.begin();
it != header.mMaster.end(); ++it)
{
profile.mContentFiles.push_back(it->name);
}
profile.mDescription = esm.getDesc();
profile.mInGameTime.mDay = context.mDay;
profile.mInGameTime.mGameHour = context.mHour;
profile.mInGameTime.mMonth = context.mMonth;
profile.mInGameTime.mYear = context.mYear;
profile.mPlayerCell = header.mGameData.mCurrentCell.toString();
if (context.mPlayerBase.mClass == "NEWCLASSID_CHARGEN")
profile.mPlayerClassName = context.mCustomPlayerClassName;
else
profile.mPlayerClassId = context.mPlayerBase.mClass;
profile.mPlayerLevel = context.mPlayerBase.mNpdt52.mLevel;
profile.mPlayerName = header.mGameData.mPlayerName.toString();
writeScreenshot(header, profile);
writer.startRecord (ESM::REC_SAVE);
profile.save (writer);
writer.endRecord (ESM::REC_SAVE);
// Writing order should be Dynamic Store -> Cells -> Player,
// so that references to dynamic records can be recognized when loading
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
it != converters.end(); ++it)
{
if (it->second->getStage() != 0)
continue;
it->second->write(writer);
}
writer.startRecord(ESM::REC_NPC_);
writer.writeHNString("NAME", "player");
context.mPlayerBase.save(writer);
writer.endRecord(ESM::REC_NPC_);
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
it != converters.end(); ++it)
{
if (it->second->getStage() != 1)
continue;
it->second->write(writer);
}
writer.startRecord(ESM::REC_PLAY);
if (context.mPlayer.mCellId.mPaged)
{
// exterior cell -> determine cell coordinates based on position
const int cellSize = 8192;
int cellX = std::floor(context.mPlayer.mObject.mPosition.pos[0]/cellSize);
int cellY = std::floor(context.mPlayer.mObject.mPosition.pos[1]/cellSize);
context.mPlayer.mCellId.mIndex.mX = cellX;
context.mPlayer.mCellId.mIndex.mY = cellY;
}
context.mPlayer.save(writer);
writer.endRecord(ESM::REC_PLAY);
}
}

View file

@ -0,0 +1,25 @@
#ifndef OPENMW_ESSIMPORTER_IMPORTER_H
#define OPENMW_ESSIMPORTER_IMPORTER_H
#include <string>
namespace ESSImport
{
class Importer
{
public:
Importer(const std::string& essfile, const std::string& outfile);
void run();
void compare();
private:
std::string mEssFile;
std::string mOutFile;
};
}
#endif

View file

View file

@ -0,0 +1,51 @@
#ifndef OPENMW_ESSIMPORT_CONTEXT_H
#define OPENMW_ESSIMPORT_CONTEXT_H
#include <map>
#include <components/esm/loadnpc.hpp>
#include <components/esm/player.hpp>
#include "importnpcc.hpp"
#include "importcrec.hpp"
#include "importplayer.hpp"
namespace ESSImport
{
struct Context
{
// set from the TES3 header
std::string mPlayerCellName;
ESM::Player mPlayer;
ESM::NPC mPlayerBase;
std::string mCustomPlayerClassName;
int mDay, mMonth, mYear;
float mHour;
// key <refIndex, refId>
std::map<std::pair<int, std::string>, CREC> mCreatureChanges;
std::map<std::pair<int, std::string>, NPCC> mNpcChanges;
Context()
{
mPlayer.mAutoMove = 0;
ESM::CellId playerCellId;
playerCellId.mPaged = true;
playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0;
mPlayer.mCellId = playerCellId;
//mPlayer.mLastKnownExteriorPosition
mPlayer.mHasMark = 0; // TODO
mPlayer.mCurrentCrimeId = 0; // TODO
mPlayer.mObject.blank();
mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame
}
};
}
#endif

View file

@ -0,0 +1,46 @@
#include "importinventory.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void Inventory::load(ESM::ESMReader &esm)
{
while (esm.isNextSub("NPCO"))
{
InventoryItem item;
item.mId = esm.getHString();
if (esm.isNextSub("XIDX"))
esm.skipHSub();
std::string script = esm.getHNOString("SCRI");
// script variables?
// unsure if before or after ESM::CellRef
if (!script.empty())
{
if (esm.isNextSub("SLCS"))
esm.skipHSub();
if (esm.isNextSub("SLSD")) // Short Data?
esm.skipHSub();
if (esm.isNextSub("SLFD")) // Float Data?
esm.skipHSub();
}
// for XSOL and XCHG seen so far, but probably others too
item.ESM::CellRef::loadData(esm);
item.mCondition = -1;
esm.getHNOT(item.mCondition, "XHLT");
mItems.push_back(item);
}
while (esm.isNextSub("WIDX"))
{
// equipping?
esm.skipHSub();
}
}
}

View file

@ -0,0 +1,31 @@
#ifndef OPENMW_ESSIMPORT_IMPORTINVENTORY_H
#define OPENMW_ESSIMPORT_IMPORTINVENTORY_H
#include <vector>
#include <string>
#include <components/esm/cellref.hpp>
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
struct Inventory
{
struct InventoryItem : public ESM::CellRef
{
std::string mId;
int mCondition;
};
std::vector<InventoryItem> mItems;
void load(ESM::ESMReader& esm);
};
}
#endif

View file

@ -0,0 +1,22 @@
#include "importklst.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void KLST::load(ESM::ESMReader &esm)
{
while (esm.isNextSub("KNAM"))
{
std::string refId = esm.getHString();
int count;
esm.getHNT(count, "CNAM");
mKillCounter[refId] = count;
}
mWerewolfKills = 0;
esm.getHNOT(mWerewolfKills, "INTV");
}
}

View file

@ -0,0 +1,28 @@
#ifndef OPENMW_ESSIMPORT_KLST_H
#define OPENMW_ESSIMPORT_KLST_H
#include <string>
#include <map>
namespace ESM
{
struct ESMReader;
}
namespace ESSImport
{
/// Kill Stats
struct KLST
{
void load(ESM::ESMReader& esm);
/// RefId, kill count
std::map<std::string, int> mKillCounter;
int mWerewolfKills;
};
}
#endif

View file

@ -0,0 +1,25 @@
#include "importnpcc.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void NPCC::load(ESM::ESMReader &esm)
{
mIndex = 0;
esm.getHNOT(mIndex, "INDX");
esm.getHNT(mNPDT, "NPDT");
if (esm.isNextSub("AI_E"))
esm.skipHSub();
if (esm.isNextSub("AI_T"))
esm.skipHSub();
if (esm.isNextSub("AI_F"))
esm.skipHSub();
mInventory.load(esm);
}
}

View file

@ -0,0 +1,36 @@
#ifndef OPENMW_ESSIMPORT_NPCC_H
#define OPENMW_ESSIMPORT_NPCC_H
#include <components/esm/loadcont.hpp>
#include <components/esm/aipackage.hpp>
#include "importinventory.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
struct NPCC
{
struct NPDT
{
unsigned char unknown[2];
unsigned char mReputation;
unsigned char unknown2[5];
} mNPDT;
Inventory mInventory;
int mIndex;
void load(ESM::ESMReader &esm);
};
}
#endif

View file

@ -0,0 +1,75 @@
#include "importplayer.hpp"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void REFR::load(ESM::ESMReader &esm)
{
esm.getHNT(mRefNum.mIndex, "FRMR");
mRefID = esm.getHNString("NAME");
mActorData.load(esm);
esm.getHNOT(mPos, "DATA", 24);
}
void PCDT::load(ESM::ESMReader &esm)
{
while (esm.isNextSub("DNAM"))
{
// TODO: deal with encoding?
mKnownDialogueTopics.push_back(esm.getHString());
}
if (esm.isNextSub("PNAM"))
esm.skipHSub();
if (esm.isNextSub("SNAM"))
esm.skipHSub();
if (esm.isNextSub("NAM9"))
esm.skipHSub();
mBounty = 0;
esm.getHNOT(mBounty, "CNAM");
mBirthsign = esm.getHNOString("BNAM");
// Holds the names of the last used Alchemy apparatus. Don't need to import this ATM,
// because our GUI auto-selects the best apparatus.
if (esm.isNextSub("NAM0"))
esm.skipHSub();
if (esm.isNextSub("NAM1"))
esm.skipHSub();
if (esm.isNextSub("NAM2"))
esm.skipHSub();
if (esm.isNextSub("NAM3"))
esm.skipHSub();
if (esm.isNextSub("ENAM"))
esm.skipHSub();
if (esm.isNextSub("LNAM"))
esm.skipHSub();
while (esm.isNextSub("FNAM"))
{
FNAM fnam;
esm.getHT(fnam);
mFactions.push_back(fnam);
}
if (esm.isNextSub("KNAM"))
esm.skipHSub();
if (esm.isNextSub("WERE"))
{
// some werewolf data, 152 bytes
// maybe current skills and attributes for werewolf form
esm.getSubHeader();
esm.skip(152);
}
}
}

View file

@ -0,0 +1,57 @@
#ifndef OPENMW_ESSIMPORT_PLAYER_H
#define OPENMW_ESSIMPORT_PLAYER_H
#include <vector>
#include <string>
#include <components/esm/defs.hpp>
#include <components/esm/cellref.hpp>
#include <components/esm/esmcommon.hpp>
#include "importacdt.hpp"
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
/// Player-agnostic player data
struct REFR
{
ActorData mActorData;
std::string mRefID;
ESM::Position mPos;
ESM::RefNum mRefNum;
void load(ESM::ESMReader& esm);
};
/// Other player data
struct PCDT
{
int mBounty;
std::string mBirthsign;
std::vector<std::string> mKnownDialogueTopics;
struct FNAM
{
unsigned char mRank;
unsigned char mUnknown1[3];
int mReputation;
unsigned char mFlags; // 0x1: unknown, 0x2: expelled
unsigned char mUnknown2[3];
ESM::NAME32 mFactionName;
};
std::vector<FNAM> mFactions;
void load(ESM::ESMReader& esm);
};
}
#endif

60
apps/essimporter/main.cpp Normal file
View file

@ -0,0 +1,60 @@
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include "importer.hpp"
namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
int main(int argc, const char** argv)
{
try
{
bpo::options_description desc("Syntax: openmw-essimporter <options> infile.ess outfile.omwsave\nAllowed options");
bpo::positional_options_description p_desc;
desc.add_options()
("help,h", "produce help message")
("mwsave,m", bpo::value<std::string>(), "morrowind .ess save file")
("output,o", bpo::value<std::string>(), "output file (.omwsave)")
("compare,c", "compare two .ess files")
;
p_desc.add("mwsave", 1).add("output", 1);
bpo::variables_map vm;
bpo::parsed_options parsed = bpo::command_line_parser(argc, argv)
.options(desc)
.positional(p_desc)
.run();
bpo::store(parsed, vm);
if(vm.count("help") || !vm.count("mwsave") || !vm.count("output")) {
std::cout << desc;
return 0;
}
bpo::notify(vm);
std::string essFile = vm["mwsave"].as<std::string>();
std::string outputFile = vm["output"].as<std::string>();
ESSImport::Importer importer(essFile, outputFile);
if (vm.count("compare"))
importer.compare();
else
importer.run();
}
catch (std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}

View file

@ -86,35 +86,16 @@ namespace
namespace MWGui
{
void CustomMarker::save(ESM::ESMWriter &esm) const
{
esm.writeHNT("POSX", mWorldX);
esm.writeHNT("POSY", mWorldY);
mCell.save(esm);
if (!mNote.empty())
esm.writeHNString("NOTE", mNote);
}
void CustomMarker::load(ESM::ESMReader &esm)
{
esm.getHNT(mWorldX, "POSX");
esm.getHNT(mWorldY, "POSY");
mCell.load(esm);
mNote = esm.getHNOString("NOTE");
}
// ------------------------------------------------------
void CustomMarkerCollection::addMarker(const CustomMarker &marker, bool triggerEvent)
void CustomMarkerCollection::addMarker(const ESM::CustomMarker &marker, bool triggerEvent)
{
mMarkers.push_back(marker);
if (triggerEvent)
eventMarkersChanged();
}
void CustomMarkerCollection::deleteMarker(const CustomMarker &marker)
void CustomMarkerCollection::deleteMarker(const ESM::CustomMarker &marker)
{
std::vector<CustomMarker>::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker);
std::vector<ESM::CustomMarker>::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker);
if (it != mMarkers.end())
mMarkers.erase(it);
else
@ -123,9 +104,9 @@ namespace MWGui
eventMarkersChanged();
}
void CustomMarkerCollection::updateMarker(const CustomMarker &marker, const std::string &newNote)
void CustomMarkerCollection::updateMarker(const ESM::CustomMarker &marker, const std::string &newNote)
{
std::vector<CustomMarker>::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker);
std::vector<ESM::CustomMarker>::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker);
if (it != mMarkers.end())
it->mNote = newNote;
else
@ -140,12 +121,12 @@ namespace MWGui
eventMarkersChanged();
}
std::vector<CustomMarker>::const_iterator CustomMarkerCollection::begin() const
std::vector<ESM::CustomMarker>::const_iterator CustomMarkerCollection::begin() const
{
return mMarkers.begin();
}
std::vector<CustomMarker>::const_iterator CustomMarkerCollection::end() const
std::vector<ESM::CustomMarker>::const_iterator CustomMarkerCollection::end() const
{
return mMarkers.end();
}
@ -295,9 +276,9 @@ namespace MWGui
MyGUI::Gui::getInstance().destroyWidget(*it);
mCustomMarkerWidgets.clear();
for (std::vector<CustomMarker>::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it)
for (std::vector<ESM::CustomMarker>::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it)
{
const CustomMarker& marker = *it;
const ESM::CustomMarker& marker = *it;
if (marker.mCell.mPaged != !mInterior)
continue;
@ -654,7 +635,7 @@ namespace MWGui
void MapWindow::onCustomMarkerDoubleClicked(MyGUI::Widget *sender)
{
mEditingMarker = *sender->getUserData<CustomMarker>();
mEditingMarker = *sender->getUserData<ESM::CustomMarker>();
mEditNoteDialog.setText(mEditingMarker.mNote);
mEditNoteDialog.showDeleteButton(true);
mEditNoteDialog.setVisible(true);

View file

@ -7,6 +7,8 @@
#include <components/esm/cellid.hpp>
#include <components/esm/custommarkerstate.hpp>
namespace MWRender
{
class GlobalMap;
@ -26,43 +28,25 @@ namespace Loading
namespace MWGui
{
struct CustomMarker
{
float mWorldX;
float mWorldY;
ESM::CellId mCell;
std::string mNote;
bool operator == (const CustomMarker& other)
{
return mNote == other.mNote && mCell == other.mCell && mWorldX == other.mWorldX && mWorldY == other.mWorldY;
}
void load (ESM::ESMReader& reader);
void save (ESM::ESMWriter& writer) const;
};
class CustomMarkerCollection
{
public:
void addMarker(const CustomMarker& marker, bool triggerEvent=true);
void deleteMarker (const CustomMarker& marker);
void updateMarker(const CustomMarker& marker, const std::string& newNote);
void addMarker(const ESM::CustomMarker& marker, bool triggerEvent=true);
void deleteMarker (const ESM::CustomMarker& marker);
void updateMarker(const ESM::CustomMarker& marker, const std::string& newNote);
void clear();
size_t size() const;
std::vector<CustomMarker>::const_iterator begin() const;
std::vector<CustomMarker>::const_iterator end() const;
std::vector<ESM::CustomMarker>::const_iterator begin() const;
std::vector<ESM::CustomMarker>::const_iterator end() const;
typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
EventHandle_Void eventMarkersChanged;
private:
std::vector<CustomMarker> mMarkers;
std::vector<ESM::CustomMarker> mMarkers;
};
class LocalMapBase
@ -233,7 +217,7 @@ namespace MWGui
MWRender::GlobalMap* mGlobalMapRender;
EditNoteDialog mEditNoteDialog;
CustomMarker mEditingMarker;
ESM::CustomMarker mEditingMarker;
virtual void onPinToggled();
virtual void onTitleDoubleClicked();

View file

@ -1612,7 +1612,7 @@ namespace MWGui
writer.endRecord(ESM::REC_ASPL);
}
for (std::vector<CustomMarker>::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it)
for (std::vector<ESM::CustomMarker>::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it)
{
writer.startRecord(ESM::REC_MARK);
(*it).save(writer);
@ -1633,7 +1633,7 @@ namespace MWGui
}
else if (type == ESM::REC_MARK)
{
CustomMarker marker;
ESM::CustomMarker marker;
marker.load(reader);
mCustomMarkers.addMarker(marker, false);
}

View file

@ -33,7 +33,6 @@ MWMechanics::NpcStats::NpcStats()
, mWerewolfKills (0)
, mProfit(0)
, mTimeToStartDrowning(20.0)
, mLastDrowningHit(0)
{
mSkillIncreases.resize (ESM::Attribute::Length, 0);
}
@ -522,7 +521,6 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const
std::copy (mUsedIds.begin(), mUsedIds.end(), std::back_inserter (state.mUsedIds));
state.mTimeToStartDrowning = mTimeToStartDrowning;
state.mLastDrowningHit = mLastDrowningHit;
}
void MWMechanics::NpcStats::readState (const ESM::NpcStats& state)
@ -573,5 +571,4 @@ void MWMechanics::NpcStats::readState (const ESM::NpcStats& state)
mUsedIds.insert (*iter);
mTimeToStartDrowning = state.mTimeToStartDrowning;
mLastDrowningHit = state.mLastDrowningHit;
}

View file

@ -46,8 +46,6 @@ namespace MWMechanics
/// Countdown to getting damage while underwater
float mTimeToStartDrowning;
/// time since last hit from drowning
float mLastDrowningHit;
public:
@ -110,8 +108,10 @@ namespace MWMechanics
/// Called at character creation.
void flagAsUsed (const std::string& id);
///< @note Id must be lower-case
bool hasBeenUsed (const std::string& id) const;
///< @note Id must be lower-case
int getBounty() const;

View file

@ -426,7 +426,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
default:
// ignore invalid records
std::cerr << "Ignoring unknown record: " << n.name << std::endl;
std::cerr << "Ignoring unknown record: " << n.toString() << std::endl;
reader.skipRecord();
}
int progressPercent = static_cast<int>(float(reader.getFileOffset())/total*100);