Added LAND, PGRD and CELL

actorid
Nicolay Korslund 15 years ago
parent 5cfe2e2de0
commit 514fe3795a

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

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

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

@ -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…
Cancel
Save