Merge remote-tracking branch 'scrawl/essimporter'
commit
a266dffb4b
@ -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()
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -0,0 +1,10 @@
|
||||
#include "convertnpcc.hpp"
|
||||
|
||||
namespace ESSImport
|
||||
{
|
||||
|
||||
void convertNPCC(const NPCC &npcc, ESM::NpcState &npcState)
|
||||
{
|
||||
npcState.mNpcStats.mReputation = npcc.mNPDT.mReputation;
|
||||
}
|
||||
}
|
@ -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
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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
|
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
#include "custommarkerstate.hpp"
|
||||
|
||||
#include "esmwriter.hpp"
|
||||
#include "esmreader.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
#ifndef OPENMW_ESM_CUSTOMMARKERSTATE_H
|
||||
#define OPENMW_ESM_CUSTOMMARKERSTATE_H
|
||||
|
||||
#include "cellid.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
||||
// format 0, saved games only
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue