forked from teamnwah/openmw-tes3coop
Added LAND, PGRD and CELL
This commit is contained in:
parent
5cfe2e2de0
commit
514fe3795a
6 changed files with 324 additions and 334 deletions
|
@ -76,6 +76,61 @@ typedef NAME_T<32> NAME32;
|
|||
typedef NAME_T<64> NAME64;
|
||||
typedef NAME_T<256> NAME256;
|
||||
|
||||
#pragma pack(push)
|
||||
#pragma pack(1)
|
||||
/// File header data for all ES files
|
||||
struct HEDRstruct
|
||||
{
|
||||
/* File format version. This is actually a float, the supported
|
||||
versions are 1.2 and 1.3. These correspond to:
|
||||
1.2 = 0x3f99999a and 1.3 = 0x3fa66666
|
||||
*/
|
||||
int version;
|
||||
int type; // 0=esp, 1=esm, 32=ess
|
||||
NAME32 author; // Author's name
|
||||
NAME256 desc; // File description
|
||||
int records; // Number of records? Not used.
|
||||
};
|
||||
|
||||
// Defines another files (esm or esp) that this file depends upon.
|
||||
struct MasterData
|
||||
{
|
||||
std::string name;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
// Data that is only present in save game files
|
||||
struct SaveData
|
||||
{
|
||||
float pos[6]; // Player position and rotation
|
||||
NAME64 cell; // Cell name
|
||||
float unk2; // Unknown value - possibly game time?
|
||||
NAME32 player; // Player name
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
/* This struct defines a file 'context' which can be saved and later
|
||||
restored by an ESMReader instance. It will save the position within
|
||||
a file, and when restored will let you read from that position as
|
||||
if you never left it.
|
||||
*/
|
||||
struct ESM_Context
|
||||
{
|
||||
std::string filename;
|
||||
uint32_t leftRec, leftSub;
|
||||
size_t leftFile;
|
||||
NAME recName, subName;
|
||||
HEDRstruct header;
|
||||
|
||||
// True if subName has been read but not used.
|
||||
bool subCached;
|
||||
|
||||
// File position. Only used for stored contexts, not regularly
|
||||
// updated within the reader itself.
|
||||
size_t filePos;
|
||||
};
|
||||
|
||||
class ESMReader
|
||||
{
|
||||
public:
|
||||
|
@ -86,39 +141,6 @@ public:
|
|||
*
|
||||
*************************************************************************/
|
||||
|
||||
#pragma pack(push)
|
||||
#pragma pack(1)
|
||||
struct HEDRstruct
|
||||
{
|
||||
/* File format version. This is actually a float, the supported
|
||||
versions are 1.2 and 1.3. These correspond to:
|
||||
1.2 = 0x3f99999a and 1.3 = 0x3fa66666
|
||||
*/
|
||||
int version;
|
||||
int type; // 0=esp, 1=esm, 32=ess
|
||||
NAME32 author; // Author's name
|
||||
NAME256 desc; // File description
|
||||
int records; // Number of records? Not used.
|
||||
};
|
||||
|
||||
// Defines another files (esm or esp) that this file depends upon.
|
||||
struct MasterData
|
||||
{
|
||||
std::string name;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
// Data that is only present in save game files
|
||||
struct SaveData
|
||||
{
|
||||
float pos[6]; // Player position and rotation
|
||||
NAME64 cell; // Cell name
|
||||
float unk2; // Unknown value - possibly game time?
|
||||
NAME32 player; // Player name
|
||||
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef std::vector<MasterData> MasterList;
|
||||
|
||||
/*************************************************************************
|
||||
|
@ -127,15 +149,15 @@ public:
|
|||
*
|
||||
*************************************************************************/
|
||||
|
||||
int getVer() { return header.version; }
|
||||
float getFVer() { return *((float*)&header.version); }
|
||||
int getVer() { return c.header.version; }
|
||||
float getFVer() { return *((float*)&c.header.version); }
|
||||
int getSpecial() { return spf; }
|
||||
const std::string getAuthor() { return header.author.toString(); }
|
||||
const std::string getDesc() { return header.desc.toString(); }
|
||||
const std::string getAuthor() { return c.header.author.toString(); }
|
||||
const std::string getDesc() { return c.header.desc.toString(); }
|
||||
const SaveData &getSaveData() { return saveData; }
|
||||
const MasterList &getMasters() { return masters; }
|
||||
NAME retSubName() { return subName; }
|
||||
uint32_t getSubSize() { return leftSub; }
|
||||
NAME retSubName() { return c.subName; }
|
||||
uint32_t getSubSize() { return c.leftSub; }
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
|
@ -143,18 +165,43 @@ public:
|
|||
*
|
||||
*************************************************************************/
|
||||
|
||||
/// Close the file, resets all information. After calling close()
|
||||
/// the structure may be reused to load a new file.
|
||||
/** Save the current file position and information in a ESM_Context
|
||||
struct
|
||||
*/
|
||||
ESM_Context getContext()
|
||||
{
|
||||
// Update the file position before returning
|
||||
c.filePos = esm->tell();
|
||||
return c;
|
||||
}
|
||||
|
||||
/** Restore a previously saved context */
|
||||
void restoreContext(const ESM_Context &rc)
|
||||
{
|
||||
// Reopen the file if necessary
|
||||
if(c.filename != rc.filename)
|
||||
openRaw(rc.filename);
|
||||
|
||||
// Copy the data
|
||||
c = rc;
|
||||
|
||||
// Make sure we seek to the right place
|
||||
esm->seek(c.filePos);
|
||||
}
|
||||
|
||||
/** Close the file, resets all information. After calling close()
|
||||
the structure may be reused to load a new file.
|
||||
*/
|
||||
void close()
|
||||
{
|
||||
esm.reset();
|
||||
filename.clear();
|
||||
leftFile = 0;
|
||||
leftRec = 0;
|
||||
leftSub = 0;
|
||||
subCached = false;
|
||||
recName.val = 0;
|
||||
subName.val = 0;
|
||||
c.filename.clear();
|
||||
c.leftFile = 0;
|
||||
c.leftRec = 0;
|
||||
c.leftSub = 0;
|
||||
c.subCached = false;
|
||||
c.recName.val = 0;
|
||||
c.subName.val = 0;
|
||||
}
|
||||
|
||||
/// Raw opening. Opens the file and sets everything up but doesn't
|
||||
|
@ -163,12 +210,12 @@ public:
|
|||
{
|
||||
close();
|
||||
esm = _esm;
|
||||
filename = name;
|
||||
leftFile = esm->size();
|
||||
c.filename = name;
|
||||
c.leftFile = esm->size();
|
||||
|
||||
// Flag certain files for special treatment, based on the file
|
||||
// name.
|
||||
const char *cstr = filename.c_str();
|
||||
const char *cstr = c.filename.c_str();
|
||||
if(iends(cstr, "Morrowind.esm")) spf = SF_Morrowind;
|
||||
else if(iends(cstr, "Tribunal.esm")) spf = SF_Tribunal;
|
||||
else if(iends(cstr, "Bloodmoon.esm")) spf = SF_Bloodmoon;
|
||||
|
@ -187,10 +234,10 @@ public:
|
|||
getRecHeader();
|
||||
|
||||
// Get the header
|
||||
getHNT(header, "HEDR", 300);
|
||||
getHNT(c.header, "HEDR", 300);
|
||||
|
||||
if(header.version != VER_12 &&
|
||||
header.version != VER_13)
|
||||
if(c.header.version != VER_12 &&
|
||||
c.header.version != VER_13)
|
||||
fail("Unsupported file format version");
|
||||
|
||||
while(isNextSub("MAST"))
|
||||
|
@ -201,7 +248,7 @@ public:
|
|||
masters.push_back(m);
|
||||
}
|
||||
|
||||
if(header.type == FT_ESS)
|
||||
if(c.header.type == FT_ESS)
|
||||
{
|
||||
// Savegame-related data
|
||||
|
||||
|
@ -282,7 +329,7 @@ public:
|
|||
void getHT(X &x)
|
||||
{
|
||||
getSubHeader();
|
||||
if(leftSub != sizeof(X))
|
||||
if(c.leftSub != sizeof(X))
|
||||
fail("getHT(): subrecord size mismatch");
|
||||
getT(x);
|
||||
}
|
||||
|
@ -321,23 +368,23 @@ public:
|
|||
// them. For some reason, they break the rules, and contain a byte
|
||||
// (value 0) even if the header says there is no data. If
|
||||
// Morrowind accepts it, so should we.
|
||||
if(leftSub == 0)
|
||||
if(c.leftSub == 0)
|
||||
{
|
||||
// Skip the following zero byte
|
||||
leftRec--;
|
||||
c.leftRec--;
|
||||
char c;
|
||||
esm->read(&c,1);
|
||||
return "";
|
||||
}
|
||||
|
||||
return getString(leftSub);
|
||||
return getString(c.leftSub);
|
||||
}
|
||||
|
||||
// Read the given number of bytes from a subrecord
|
||||
void getHExact(void*p, int size)
|
||||
{
|
||||
getSubHeader();
|
||||
if(size != leftSub)
|
||||
if(size != c.leftSub)
|
||||
fail("getHExact() size mismatch");
|
||||
getExact(p,size);
|
||||
}
|
||||
|
@ -359,8 +406,8 @@ public:
|
|||
void getSubNameIs(const char* name)
|
||||
{
|
||||
getSubName();
|
||||
if(subName != name)
|
||||
fail("Expected subrecord " + std::string(name) + " but got " + subName.toString());
|
||||
if(c.subName != name)
|
||||
fail("Expected subrecord " + std::string(name) + " but got " + c.subName.toString());
|
||||
}
|
||||
|
||||
/** Checks if the next sub record name matches the parameter. If it
|
||||
|
@ -370,16 +417,16 @@ public:
|
|||
*/
|
||||
bool isNextSub(const char* name)
|
||||
{
|
||||
if(!leftRec) return false;
|
||||
if(!c.leftRec) return false;
|
||||
|
||||
getSubName();
|
||||
|
||||
// If the name didn't match, then mark the it as 'cached' so it's
|
||||
// available for the next call to getSubName.
|
||||
subCached = (subName != name);
|
||||
c.subCached = (c.subName != name);
|
||||
|
||||
// If subCached is false, then subName == name.
|
||||
return !subCached;
|
||||
return !c.subCached;
|
||||
}
|
||||
|
||||
// Read subrecord name. This gets called a LOT, so I've optimized it
|
||||
|
@ -387,16 +434,16 @@ public:
|
|||
void getSubName()
|
||||
{
|
||||
// If the name has already been read, do nothing
|
||||
if(subCached)
|
||||
if(c.subCached)
|
||||
{
|
||||
subCached = false;
|
||||
c.subCached = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't bother with error checking, we will catch an EOF upon
|
||||
// reading the subrecord data anyway.
|
||||
esm->read(subName.name, 4);
|
||||
leftRec -= 4;
|
||||
esm->read(c.subName.name, 4);
|
||||
c.leftRec -= 4;
|
||||
}
|
||||
|
||||
// Skip current sub record, including header (but not including
|
||||
|
@ -404,14 +451,14 @@ public:
|
|||
void skipHSub()
|
||||
{
|
||||
getSubHeader();
|
||||
skip(leftSub);
|
||||
skip(c.leftSub);
|
||||
}
|
||||
|
||||
// Skip sub record and check its size
|
||||
void skipHSubSize(int size)
|
||||
{
|
||||
skipHSub();
|
||||
if(leftSub != size)
|
||||
if(c.leftSub != size)
|
||||
fail("skipHSubSize() mismatch");
|
||||
}
|
||||
|
||||
|
@ -420,17 +467,17 @@ public:
|
|||
*/
|
||||
void getSubHeader()
|
||||
{
|
||||
if(leftRec < 4)
|
||||
if(c.leftRec < 4)
|
||||
fail("End of record while reading sub-record header");
|
||||
|
||||
// Get subrecord size
|
||||
getT(leftSub);
|
||||
getT(c.leftSub);
|
||||
|
||||
// Adjust number of record bytes left
|
||||
leftRec -= leftSub + 4;
|
||||
c.leftRec -= c.leftSub + 4;
|
||||
|
||||
// Check that sizes added up
|
||||
if(leftRec < 0)
|
||||
if(c.leftRec < 0)
|
||||
fail("Not enough bytes left in record for this subrecord.");
|
||||
}
|
||||
|
||||
|
@ -439,7 +486,7 @@ public:
|
|||
void getSubHeaderIs(int size)
|
||||
{
|
||||
getSubHeader();
|
||||
if(size != leftSub)
|
||||
if(size != c.leftSub)
|
||||
fail("getSubHeaderIs(): Sub header mismatch");
|
||||
}
|
||||
|
||||
|
@ -454,29 +501,29 @@ public:
|
|||
{
|
||||
if(!hasMoreRecs())
|
||||
fail("No more records, getRecName() failed");
|
||||
getName(recName);
|
||||
leftFile -= 4;
|
||||
getName(c.recName);
|
||||
c.leftFile -= 4;
|
||||
|
||||
// Make sure we don't carry over any old cached subrecord
|
||||
// names. This can happen in some cases when we skip parts of a
|
||||
// record.
|
||||
subCached = false;
|
||||
c.subCached = false;
|
||||
|
||||
return recName;
|
||||
return c.recName;
|
||||
}
|
||||
|
||||
// Skip the rest of this record. Assumes the name and header have
|
||||
// already been read
|
||||
void skipRecord()
|
||||
{
|
||||
skip(leftRec);
|
||||
leftRec = 0;
|
||||
skip(c.leftRec);
|
||||
c.leftRec = 0;
|
||||
}
|
||||
|
||||
// Skip an entire record, including the header (but not the name)
|
||||
void skipHRecord()
|
||||
{
|
||||
if(!leftFile) return;
|
||||
if(!c.leftFile) return;
|
||||
getRecHeader();
|
||||
skipRecord();
|
||||
}
|
||||
|
@ -489,26 +536,26 @@ public:
|
|||
void getRecHeader(uint32_t &flags)
|
||||
{
|
||||
// General error checking
|
||||
if(leftFile < 12)
|
||||
if(c.leftFile < 12)
|
||||
fail("End of file while reading record header");
|
||||
if(leftRec)
|
||||
if(c.leftRec)
|
||||
fail("Previous record contains unread bytes");
|
||||
|
||||
getUint(leftRec);
|
||||
getUint(c.leftRec);
|
||||
getUint(flags);// This header entry is always zero
|
||||
getUint(flags);
|
||||
leftFile -= 12;
|
||||
c.leftFile -= 12;
|
||||
|
||||
// Check that sizes add up
|
||||
if(leftFile < leftRec)
|
||||
if(c.leftFile < c.leftRec)
|
||||
fail("Record size is larger than rest of file");
|
||||
|
||||
// Adjust number of bytes left in file
|
||||
leftFile -= leftRec;
|
||||
// Adjust number of bytes c.left in file
|
||||
c.leftFile -= c.leftRec;
|
||||
}
|
||||
|
||||
bool hasMoreRecs() { return leftFile > 0; }
|
||||
bool hasMoreSubs() { return leftRec > 0; }
|
||||
bool hasMoreRecs() { return c.leftFile > 0; }
|
||||
bool hasMoreSubs() { return c.leftRec > 0; }
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
|
@ -551,9 +598,9 @@ public:
|
|||
stringstream ss;
|
||||
|
||||
ss << "ESM Error: " << msg;
|
||||
ss << "\n File: " << filename;
|
||||
ss << "\n Record: " << recName.toString();
|
||||
ss << "\n Subrecord: " << subName.toString();
|
||||
ss << "\n File: " << c.filename;
|
||||
ss << "\n Record: " << c.recName.toString();
|
||||
ss << "\n Subrecord: " << c.subName.toString();
|
||||
if(esm != NULL)
|
||||
ss << "\n Offset: 0x" << hex << esm->tell();
|
||||
throw str_exception(ss.str());
|
||||
|
@ -561,19 +608,14 @@ public:
|
|||
|
||||
private:
|
||||
Mangle::Stream::StreamPtr esm;
|
||||
size_t leftFile;
|
||||
uint32_t leftRec, leftSub;
|
||||
std::string filename;
|
||||
|
||||
NAME recName, subName;
|
||||
ESM_Context c;
|
||||
|
||||
// True if subName has been read but not used.
|
||||
bool subCached;
|
||||
// Special file signifier (see SpecialFile enum above)
|
||||
int spf;
|
||||
|
||||
HEDRstruct header;
|
||||
SaveData saveData;
|
||||
MasterList masters;
|
||||
int spf; // Special file signifier (see SpecialFile below)
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
|
64
esm/loadcell.hpp
Normal file
64
esm/loadcell.hpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
#ifndef _ESM_CELL_H
|
||||
#define _ESM_CELL_H
|
||||
|
||||
#include "esm_reader.hpp"
|
||||
|
||||
namespace ESM {
|
||||
|
||||
/* Cells hold data about objects, creatures, statics (rocks, walls,
|
||||
* buildings) and landscape (for exterior cells). Cells frequently
|
||||
* also has other associated LAND and PGRD records. Combined, all this
|
||||
* data can be huge, and we cannot load it all at startup. Instead,
|
||||
* the strategy we use is to remember the file position of each cell
|
||||
* (using ESMReader::getContext()) and jumping back into place
|
||||
* whenever we need to load a given cell.
|
||||
*/
|
||||
|
||||
struct Cell
|
||||
{
|
||||
enum Flags
|
||||
{
|
||||
Interior = 0x01, // Interior cell
|
||||
HasWater = 0x02, // Does this cell have a water surface
|
||||
NoSleep = 0x04, // Is it allowed to sleep here (without a bed)
|
||||
QuasiEx = 0x80 // Behave like exterior (Tribunal+), with
|
||||
// skybox and weather
|
||||
};
|
||||
|
||||
struct DATAstruct
|
||||
{
|
||||
int flags;
|
||||
int gridX, gridY;
|
||||
};
|
||||
|
||||
// Interior cells are indexed by this (it's the 'id'), for exterior
|
||||
// cells it is optional.
|
||||
std::string name,
|
||||
|
||||
// Optional region name for exterior cells.
|
||||
region;
|
||||
|
||||
// File position
|
||||
ESM_Context context;
|
||||
|
||||
DATAstruct data;
|
||||
|
||||
void load(ESMReader &esm)
|
||||
{
|
||||
// This is implicit?
|
||||
//name = esm.getHNString("NAME");
|
||||
|
||||
// Ignore this for now, I assume it might mean we delete the entire cell?
|
||||
if(esm.isNextSub("DELE")) esm.skipHSub();
|
||||
|
||||
esm.getHNT(data, "DATA", 12);
|
||||
|
||||
// Save position and move on
|
||||
context = esm.getContext();
|
||||
esm.skipRecord();
|
||||
|
||||
region = esm.getHNOString("RGNN");
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
52
esm/loadland.hpp
Normal file
52
esm/loadland.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#ifndef _ESM_LAND_H
|
||||
#define _ESM_LAND_H
|
||||
|
||||
#include "esm_reader.hpp"
|
||||
|
||||
namespace ESM {
|
||||
|
||||
/*
|
||||
* Landscape data.
|
||||
*/
|
||||
|
||||
struct Land
|
||||
{
|
||||
int flags; // Only first four bits seem to be used, don't know what
|
||||
// they mean.
|
||||
int X, Y; // Map coordinates.
|
||||
|
||||
// File context. This allows the ESM reader to be 'reset' to this
|
||||
// location later when we are ready to load the full data set.
|
||||
ESM_Context context;
|
||||
|
||||
bool hasData;
|
||||
|
||||
void load(ESMReader &esm)
|
||||
{
|
||||
// Get the grid location
|
||||
esm.getSubNameIs("INTV");
|
||||
esm.getT(X);
|
||||
esm.getT(Y);
|
||||
|
||||
esm.getHNT(flags, "DATA");
|
||||
|
||||
// Store the file position
|
||||
context = esm.getContext();
|
||||
|
||||
hasData = false;
|
||||
int cnt;
|
||||
|
||||
// Skip these here. Load the actual data when the cell is loaded.
|
||||
if(esm.isNextSub("VNML")) {esm.skipHSubSize(12675);cnt++;}
|
||||
if(esm.isNextSub("VHGT")) {esm.skipHSubSize(4232);cnt++;}
|
||||
if(esm.isNextSub("WNAM")) esm.skipHSubSize(81);
|
||||
if(esm.isNextSub("VCLR")) esm.skipHSubSize(12675);
|
||||
if(esm.isNextSub("VTEX")) {esm.skipHSubSize(512);cnt++;}
|
||||
|
||||
// We need all three of VNML, VHGT and VTEX in order to use the
|
||||
// landscape.
|
||||
hasData = (cnt == 3);
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
56
esm/loadpgrd.hpp
Normal file
56
esm/loadpgrd.hpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
#ifndef _ESM_PGRD_H
|
||||
#define _ESM_PGRD_H
|
||||
|
||||
#include "esm_reader.hpp"
|
||||
|
||||
namespace ESM {
|
||||
|
||||
/*
|
||||
* Path grid.
|
||||
*/
|
||||
struct PathGrid
|
||||
{
|
||||
struct DATAstruct
|
||||
{
|
||||
int x, y; // Grid location, matches cell for exterior cells
|
||||
short s1; // ?? Usually but not always a power of 2. Doesn't seem
|
||||
// to have any relation to the size of PGRC.
|
||||
short s2; // Number of path points? Size of PGRP block is always 16 * s2;
|
||||
}; // 12 bytes
|
||||
|
||||
std::string cell; // Cell name
|
||||
DATAstruct data;
|
||||
ESM_Context context; // Context so we can return here later and
|
||||
// finish the job
|
||||
|
||||
void load(ESMReader &esm)
|
||||
{
|
||||
esm.getHNT(data, "DATA", 12);
|
||||
cell = esm.getHNString("NAME");
|
||||
|
||||
// Remember this file position
|
||||
context = esm.getContext();
|
||||
|
||||
// Check that the sizes match up. Size = 16 * s2 (path points?)
|
||||
if(esm.isNextSub("PGRP"))
|
||||
{
|
||||
esm.skipHSub();
|
||||
int size = esm.getSubSize();
|
||||
if(size != 16*data.s2)
|
||||
esm.fail("Path grid table size mismatch");
|
||||
}
|
||||
|
||||
// Size varies. Path grid chances? Connections? Multiples of 4
|
||||
// suggest either int or two shorts, or perhaps a float. Study
|
||||
// it later.
|
||||
if(esm.isNextSub("PGRC"))
|
||||
{
|
||||
esm.skipHSub();
|
||||
int size = esm.getSubSize();
|
||||
if(size % 4 != 0)
|
||||
esm.fail("PGRC size not a multiple of 4");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -8,6 +8,7 @@
|
|||
#include "loadbody.hpp"
|
||||
#include "loadbook.hpp"
|
||||
#include "loadbsgn.hpp"
|
||||
#include "loadcell.hpp"
|
||||
#include "loadclas.hpp"
|
||||
#include "loadclot.hpp"
|
||||
#include "loadcont.hpp"
|
||||
|
@ -19,12 +20,14 @@
|
|||
#include "loadglob.hpp"
|
||||
#include "loadgmst.hpp"
|
||||
#include "loadingr.hpp"
|
||||
#include "loadland.hpp"
|
||||
#include "loadligh.hpp"
|
||||
#include "loadlocks.hpp"
|
||||
#include "loadltex.hpp"
|
||||
#include "loadmgef.hpp"
|
||||
#include "loadmisc.hpp"
|
||||
#include "loadnpcc.hpp"
|
||||
#include "loadpgrd.hpp"
|
||||
#include "loadrace.hpp"
|
||||
#include "loadregn.hpp"
|
||||
#include "loadscpt.hpp"
|
||||
|
|
|
@ -1,40 +1,6 @@
|
|||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (loadcell.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module esm.loadcell;
|
||||
|
||||
import esm.imports;
|
||||
import esm.loadregn;
|
||||
|
||||
import std.math : abs;
|
||||
|
||||
int max(int x, int y)
|
||||
{ return x>=y?x:y; }
|
||||
|
||||
/* Cells can be seen as a collection of several records, and holds
|
||||
* data about objects, creatures, statics and landscape (for exterior
|
||||
* cells). This data can be huge, and has to be loaded when we need
|
||||
* it, not when parsing the file.
|
||||
This file contains some leftovers which have not yet been ported to
|
||||
C++.
|
||||
*/
|
||||
|
||||
align(1) struct AMBIStruct
|
||||
|
@ -45,101 +11,8 @@ align(1) struct AMBIStruct
|
|||
static assert(AMBIStruct.sizeof == 16);
|
||||
}
|
||||
|
||||
enum CellFlags : uint
|
||||
{
|
||||
Interior = 0x01,
|
||||
HasWater = 0x02,
|
||||
NoSleep = 0x04,
|
||||
QuasiExt = 0x80 // Behave like exterior (tribunal, requires
|
||||
// version 1.3? TODO: Check this (and for other
|
||||
// tribunal-only stuff))
|
||||
}
|
||||
|
||||
struct InteriorCell
|
||||
{
|
||||
char[] id;
|
||||
CellFlags flags;
|
||||
|
||||
LoadState state;
|
||||
|
||||
// Stores file position so we can load the cell later
|
||||
TES3FileContext context;
|
||||
|
||||
// This struct also holds a file context for use later
|
||||
PathGrid paths;
|
||||
|
||||
void load()
|
||||
{with(esFile){
|
||||
// Save the file position and skip to the next record (after the CELL)
|
||||
getContext(context);
|
||||
skipRecord();
|
||||
|
||||
// Check for path grid data
|
||||
paths.state = LoadState.Unloaded;
|
||||
if(isNextHRec("PGRD")) paths.load();
|
||||
}}
|
||||
}
|
||||
|
||||
struct ExteriorCell
|
||||
{
|
||||
// This isn't an id, so we call it 'name' instead. May be empty, if
|
||||
// so use region name to display
|
||||
char[] name;
|
||||
|
||||
CellFlags flags;
|
||||
int gridX, gridY;
|
||||
|
||||
LoadState state;
|
||||
|
||||
Region* region;
|
||||
|
||||
// We currently don't load all cell data (these can be huge!), and
|
||||
// it makes sense to only load these when accessed. Use this to
|
||||
// reopen the file later.
|
||||
TES3FileContext context;
|
||||
|
||||
// Landscape and path grid data
|
||||
Land land; // There can be TWO landscapes! Or maybe I have
|
||||
// misunderstood something. Need to check what it
|
||||
// means. UPDATE: See comment further down.
|
||||
|
||||
PathGrid paths;
|
||||
|
||||
// Return true if we have land data
|
||||
bool hasLand()
|
||||
{
|
||||
return land.state == LoadState.Loaded && land.hasData;
|
||||
}
|
||||
|
||||
void load()
|
||||
{with(esFile){
|
||||
this.region = getHNOPtr!(Region)("RGNN", regions);
|
||||
|
||||
// Save the file position and skip to the next record (after the CELL)
|
||||
getContext(context);
|
||||
skipRecord();
|
||||
|
||||
// Exterior cells might have landscape data
|
||||
land.state = LoadState.Unloaded;
|
||||
if(isNextHRec("LAND")) land.load();
|
||||
|
||||
// Check for path grid data
|
||||
paths.state = LoadState.Unloaded;
|
||||
if(isNextHRec("PGRD")) paths.load();
|
||||
|
||||
// Land can also be here instead. In fact, it can be both
|
||||
// places. I have to figure out what it means. UPDATE: Since
|
||||
// both the LAND and PGRD records have X/Y coordinates of their
|
||||
// own, a much more robust solution is to not depend on
|
||||
// order. Nevertheless, I still think there are a couple of
|
||||
// instances of duplicate LAND structures in esm files.
|
||||
if(isNextHRec("LAND")) land.load();
|
||||
|
||||
if(land.state == LoadState.Loaded)
|
||||
if(gridX != land.X || gridY != land.Y)
|
||||
writefln("WARNING: Grid mismatch at %s,%s!", gridX, gridY);
|
||||
}}
|
||||
}
|
||||
int max(int x, int y)
|
||||
{ return x>=y?x:y; }
|
||||
|
||||
struct ExtCellHash
|
||||
{
|
||||
|
@ -313,104 +186,4 @@ class CellList : ListKeeper
|
|||
}
|
||||
}}
|
||||
}
|
||||
|
||||
/*
|
||||
* Landscape data.
|
||||
*/
|
||||
|
||||
struct Land
|
||||
{
|
||||
LoadState state;
|
||||
|
||||
uint flags; // ?? - only first four bits seem to be used
|
||||
|
||||
// Map coordinates.
|
||||
int X, Y;
|
||||
|
||||
TES3FileContext context;
|
||||
|
||||
bool hasData = false;
|
||||
|
||||
void load()
|
||||
{with(esFile){
|
||||
// Get the grid location
|
||||
ulong grid = getHNUlong("INTV");
|
||||
X = grid & uint.max;
|
||||
Y = (grid >> 32);
|
||||
|
||||
flags = getHNInt("DATA");
|
||||
|
||||
// Save file position
|
||||
getContext(context);
|
||||
|
||||
int cnt;
|
||||
|
||||
// Skip these here. Load the actual data when the cell is loaded.
|
||||
if(isNextSub("VNML")) {skipHSubSize(12675);cnt++;}
|
||||
if(isNextSub("VHGT")) {skipHSubSize(4232);cnt++;}
|
||||
if(isNextSub("WNAM")) skipHSubSize(81);
|
||||
if(isNextSub("VCLR")) skipHSubSize(12675);
|
||||
if(isNextSub("VTEX")) {skipHSubSize(512);cnt++;}
|
||||
|
||||
// We need all three of VNML, VHGT and VTEX in order to use the
|
||||
// landscape.
|
||||
hasData = (cnt == 3);
|
||||
|
||||
if(state == LoadState.Loaded)
|
||||
writefln("Duplicate landscape data in " ~ getFilename());
|
||||
state = LoadState.Loaded;
|
||||
}}
|
||||
}
|
||||
|
||||
/*
|
||||
* Path grid.
|
||||
*/
|
||||
struct PathGrid
|
||||
{
|
||||
struct DATAstruct
|
||||
{
|
||||
int x, y; // Grid location, matches cell for exterior cells
|
||||
short s1; // ?? Usually but not always a power of 2. Doesn't seem
|
||||
// to have any relation to the size of PGRC.
|
||||
short s2; // Number of path points? Size of PGRP block is always 16 * s2;
|
||||
}
|
||||
|
||||
DATAstruct data;
|
||||
|
||||
TES3FileContext context;
|
||||
|
||||
LoadState state;
|
||||
|
||||
void load()
|
||||
{with(esFile){
|
||||
assert(state == LoadState.Unloaded);
|
||||
|
||||
readHNExact(&data, data.sizeof, "DATA");
|
||||
getHNString("NAME"); // Cell name, we don't really need it so just
|
||||
// ignore it.
|
||||
|
||||
// Remember this file position
|
||||
getContext(context);
|
||||
|
||||
// Check that the sizes match up. Size = 16 * s2 (path points?)
|
||||
if(isNextSub("PGRP"))
|
||||
{
|
||||
int size = skipHSub();
|
||||
if(size != 16*data.s2)
|
||||
fail("Path grid table size mismatch");
|
||||
}
|
||||
|
||||
// Size varies. Path grid chances? Connections? Multiples of 4
|
||||
// suggest either int or two shorts, or perhaps a float. Study
|
||||
// it later.
|
||||
if(isNextSub("PGRC"))
|
||||
{
|
||||
int size = skipHSub();
|
||||
if(size % 4 != 0)
|
||||
fail("PGRC size not a multiple of 4");
|
||||
}
|
||||
|
||||
state = LoadState.Loaded;
|
||||
}}
|
||||
}
|
||||
CellList cells;
|
||||
|
|
Loading…
Reference in a new issue