Merge remote-tracking branch 'scrawl/essimporter'

openmw-35
Marc Zinnschlag 10 years ago
commit a266dffb4b

@ -58,6 +58,7 @@ option(BUILD_BSATOOL "build BSA extractor" ON)
option(BUILD_ESMTOOL "build ESM inspector" ON)
option(BUILD_LAUNCHER "build Launcher" ON)
option(BUILD_MWINIIMPORTER "build MWiniImporter" ON)
option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON)
option(BUILD_OPENCS "build OpenMW Construction Set" ON)
option(BUILD_WIZARD "build Installation Wizard" ON)
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
@ -414,6 +415,9 @@ IF(NOT WIN32 AND NOT APPLE)
IF(BUILD_MWINIIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" )
ENDIF(BUILD_MWINIIMPORTER)
IF(BUILD_ESSIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-essimporter" DESTINATION "${BINDIR}" )
ENDIF(BUILD_ESSIMPORTER)
IF(BUILD_OPENCS)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" )
ENDIF(BUILD_OPENCS)
@ -472,6 +476,9 @@ if(WIN32)
IF(BUILD_MWINIIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/mwiniimport.exe" DESTINATION ".")
ENDIF(BUILD_MWINIIMPORTER)
IF(BUILD_ESSIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-essimporter.exe" DESTINATION ".")
ENDIF(BUILD_ESSIMPORTER)
IF(BUILD_OPENCS)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/opencs.exe" DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION ".")
@ -582,6 +589,10 @@ if (BUILD_MWINIIMPORTER)
add_subdirectory( apps/mwiniimporter )
endif()
if (BUILD_ESSIMPORTER)
add_subdirectory (apps/essimporter )
endif()
if (BUILD_OPENCS)
add_subdirectory (apps/opencs)
endif()

@ -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;
}

@ -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);

@ -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();

@ -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);
}

@ -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;
}

@ -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;

@ -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);

@ -63,7 +63,7 @@ add_component_dir (esm
loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter
savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate dialoguestate statstate
npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile
aisequence magiceffects util
aisequence magiceffects util custommarkerstate
)
add_component_dir (esmterrain

@ -20,6 +20,11 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum)
mRefID = esm.getHNString ("NAME");
loadData(esm);
}
void ESM::CellRef::loadData(ESMReader &esm)
{
// Again, UNAM sometimes appears after NAME and sometimes later.
// Or perhaps this UNAM means something different?
mReferenceBlocked = -1;

@ -91,6 +91,9 @@ namespace ESM
void load (ESMReader& esm, bool wideRefNum = false);
/// Implicitly called by load
void loadData (ESMReader& esm);
void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false) const;
void blank();

@ -17,4 +17,10 @@ void ESM::CreatureState::save (ESMWriter &esm, bool inInventory) const
mInventory.save (esm);
mCreatureStats.save (esm);
}
}
void ESM::CreatureState::blank()
{
ObjectState::blank();
mCreatureStats.blank();
}

@ -14,6 +14,9 @@ namespace ESM
InventoryState mInventory;
CreatureStats mCreatureStats;
/// Initialize to default state
void blank();
virtual void load (ESMReader &esm);
virtual void save (ESMWriter &esm, bool inInventory = false) const;
};

@ -218,6 +218,38 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
}
esm.writeHNT("AISE", mHasAiSettings);
for (int i=0; i<4; ++i)
mAiSettings[i].save(esm);
if (mHasAiSettings)
{
for (int i=0; i<4; ++i)
mAiSettings[i].save(esm);
}
}
void ESM::CreatureStats::blank()
{
mTradeTime.mHour = 0;
mTradeTime.mDay = 0;
mGoldPool = 0;
mActorId = 0;
mHasAiSettings = false;
mDead = false;
mDied = false;
mMurdered = false;
mFriendlyHits = 0;
mTalkedTo = false;
mAlarmed = false;
mAttacked = false;
mAttackingOrSpell = false;
mKnockdown = false;
mKnockdownOneFrame = false;
mKnockdownOverOneFrame = false;
mHitRecovery = false;
mBlock = false;
mMovementFlags = 0;
mAttackStrength = 0.f;
mFallHeight = 0.f;
mRecalcDynamicStats = false;
mDrawState = 0;
mDeathAnimation = 0;
mLevel = 1;
}

@ -66,6 +66,9 @@ namespace ESM
SpellState mSpells;
ActiveSpells mActiveSpells;
/// Initialize to default state
void blank();
void load (ESMReader &esm);
void save (ESMWriter &esm) const;
};

@ -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

@ -299,8 +299,7 @@ std::string ESMReader::getString(int size)
char *ptr = &mBuffer[0];
getExact(ptr, size);
if (size>0 && ptr[size-1]==0)
--size;
size = strnlen(ptr, size);
// Convert to UTF8 and return
if (mEncoder)

@ -36,6 +36,7 @@ public:
const std::string getAuthor() const { return mHeader.mData.author.toString(); }
const std::string getDesc() const { return mHeader.mData.desc.toString(); }
const std::vector<Header::MasterData> &getGameFiles() const { return mHeader.mMaster; }
const Header& getHeader() const { return mHeader; }
int getFormat() const;
const NAME &retSubName() const { return mCtx.subName; }
uint32_t getSubSize() const { return mCtx.leftSub; }

@ -17,26 +17,29 @@ class ESMWriter;
*
* Some general observations about savegames:
*
* Magical items/potions/spells/etc are added normally as new ALCH,
* SPEL, etc. records, with unique numeric identifiers.
*
* Books with ability enhancements are listed in the save if they have
* been read.
*
* GLOB records set global variables.
*
* SCPT records do not define new scripts, but assign values to the
* variables of existing ones.
*
* STLN - stolen items, ONAM is the owner
*
* GAME - contains a GMDT (game data) of unknown format
*
* VFXM, SPLM, KLST - no clue
* GAME - weather data
* struct GMDT
{
char mCellName[64];
int mFogColour;
float mFogDensity;
int mCurrentWeather, mNextWeather;
int mWeatherTransition; // 0-100 transition between weathers, top 3 bytes may be garbage
float mTimeOfNextTransition; // weather changes when gamehour == timeOfNextTransition
int masserPhase, secundaPhase; // top 3 bytes may be garbage
};
*
* VFXM, SPLM - no clue
* KLST - kill counter
*
* PCDT - seems to contain a lot of DNAMs, strings?
*
* FMAP - MAPH and MAPD, probably map data.
* FMAP - MAPH and MAPD, global map image.
*
* JOUR - the entire journal in html
*
@ -44,6 +47,8 @@ class ESMWriter;
* ones you have done or begun.
*
* REGN - lists all regions in the game, even unvisited ones.
* notable differences to Regions in ESM files: mMapColor may be missing, and includes an unknown WNAM subrecord.
*
*
* The DIAL/INFO blocks contain changes to characters' dialog status.
*

@ -92,6 +92,32 @@ void Script::load(ESMReader &esm)
if (esm.isNextSub("SCVR")) {
esm.skipHSub();
}
// ESS only, TODO: move
if (esm.isNextSub("SLCS"))
{
esm.getSubHeader();
char unknown[12];
esm.getExact(unknown, 12);
}
if (esm.isNextSub("SLSD"))
{
float read;
esm.getSubHeader();
// values of variables?
for (int i=0; i<mData.mNumShorts; ++i)
esm.getExact(&read, 2);
for (int i=0; i<mData.mNumLongs; ++i)
esm.getExact(&read, 4);
for (int i=0; i<mData.mNumFloats; ++i)
esm.getExact(&read, 4);
}
if (esm.isNextSub("RNAM"))
{
// RefNum? something to do with targetted scripts?
int refnum;
esm.getHT(refnum);
}
}
void Script::save(ESMWriter &esm) const
{

@ -45,6 +45,25 @@ void ESM::Header::load (ESMReader &esm)
m.size = esm.getHNLong ("DATA");
mMaster.push_back (m);
}
if (esm.isNextSub("GMDT"))
{
esm.getHT(mGameData);
}
if (esm.isNextSub("SCRD"))
{
esm.getSubHeader();
mSCRD.resize(esm.getSubSize());
if (mSCRD.size())
esm.getExact(&mSCRD[0], mSCRD.size());
}
if (esm.isNextSub("SCRS"))
{
esm.getSubHeader();
mSCRS.resize(esm.getSubSize());
if (mSCRS.size())
esm.getExact(&mSCRS[0], mSCRS.size());
}
}
void ESM::Header::save (ESMWriter &esm)

@ -39,6 +39,20 @@ namespace ESM
int index; // Position of the parent file in the global list of loaded files
};
struct GMDT
{
float mCurrentHealth;
float mMaximumHealth;
float mHour;
unsigned char unknown1[12];
NAME64 mCurrentCell;
unsigned char unknown2[4];
NAME32 mPlayerName;
};
GMDT mGameData; // Used in .ess savegames only
std::vector<unsigned char> mSCRD; // Used in .ess savegames only, screenshot?
std::vector<unsigned char> mSCRS; // Used in .ess savegames only, screenshot?
Data mData;
int mFormat;
std::vector<MasterData> mMaster;

@ -21,4 +21,11 @@ void ESM::NpcState::save (ESMWriter &esm, bool inInventory) const
mNpcStats.save (esm);
mCreatureStats.save (esm);
}
}
void ESM::NpcState::blank()
{
ObjectState::blank();
mNpcStats.blank();
mCreatureStats.blank();
}

@ -16,6 +16,9 @@ namespace ESM
NpcStats mNpcStats;
CreatureStats mCreatureStats;
/// Initialize to default state
void blank();
virtual void load (ESMReader &esm);
virtual void save (ESMWriter &esm, bool inInventory = false) const;
};

@ -75,8 +75,9 @@ void ESM::NpcStats::load (ESMReader &esm)
mTimeToStartDrowning = 0;
esm.getHNOT (mTimeToStartDrowning, "DRTI");
mLastDrowningHit = 0;
esm.getHNOT (mLastDrowningHit, "DRLH");
// No longer used
float lastDrowningHit = 0;
esm.getHNOT (lastDrowningHit, "DRLH");
// No longer used
float levelHealthBonus = 0;
@ -146,9 +147,21 @@ void ESM::NpcStats::save (ESMWriter &esm) const
if (mTimeToStartDrowning)
esm.writeHNT ("DRTI", mTimeToStartDrowning);
if (mLastDrowningHit)
esm.writeHNT ("DRLH", mLastDrowningHit);
if (mCrimeId != -1)
esm.writeHNT ("CRID", mCrimeId);
}
void ESM::NpcStats::blank()
{
mIsWerewolf = false;
mDisposition = 0;
mBounty = 0;
mReputation = 0;
mWerewolfKills = 0;
mProfit = 0;
mLevelProgress = 0;
for (int i=0; i<8; ++i)
mSkillIncrease[i] = 0;
mTimeToStartDrowning = 20;
mCrimeId = -1;
}

@ -45,9 +45,11 @@ namespace ESM
int mSkillIncrease[8];
std::vector<std::string> mUsedIds;
float mTimeToStartDrowning;
float mLastDrowningHit;
int mCrimeId;
/// Initialize to default state
void blank();
void load (ESMReader &esm);
void save (ESMWriter &esm) const;
};

@ -48,4 +48,18 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const
}
}
ESM::ObjectState::~ObjectState() {}
void ESM::ObjectState::blank()
{
mRef.blank();
mHasLocals = 0;
mEnabled = false;
mCount = 1;
for (int i=0;i<3;++i)
{
mPosition.pos[i] = 0;
mPosition.rot[i] = 0;
mLocalRotation[i] = 0;
}
}
ESM::ObjectState::~ObjectState() {}

@ -29,8 +29,11 @@ namespace ESM
virtual void load (ESMReader &esm);
virtual void save (ESMWriter &esm, bool inInventory = false) const;
/// Initialize to default state
void blank();
virtual ~ObjectState();
};
}
#endif
#endif

@ -461,7 +461,9 @@ namespace Physic
BulletShapeManager::getSingletonPtr()->load(outputstring,"General");
BulletShapePtr shape = BulletShapeManager::getSingleton().getByName(outputstring,"General");
if (placeable && !raycasting && shape->mCollisionShape && shape->mAutogenerated)
// TODO: add option somewhere to enable collision for placeable meshes
if (placeable && !raycasting && shape->mCollisionShape)
return NULL;
if (!shape->mCollisionShape && !raycasting)

Loading…
Cancel
Save