Converted loadinfo.d

actorid
Nicolay Korslund 15 years ago
parent e5a64eeb5e
commit 2302e1f712

@ -68,7 +68,7 @@ union NAME_T
bool operator==(int v) { return v == val; }
bool operator!=(int v) { return v != val; }
std::string toString() { return std::string(name, strnlen(name, LEN)); }
std::string toString() const { return std::string(name, strnlen(name, LEN)); }
};
typedef NAME_T<4> NAME;
@ -156,7 +156,7 @@ public:
const std::string getDesc() { return c.header.desc.toString(); }
const SaveData &getSaveData() { return saveData; }
const MasterList &getMasters() { return masters; }
NAME retSubName() { return c.subName; }
const NAME &retSubName() { return c.subName; }
uint32_t getSubSize() { return c.leftSub; }
/*************************************************************************
@ -446,6 +446,18 @@ public:
c.leftRec -= 4;
}
// This is specially optimized for LoadINFO.
bool isEmptyOrGetName()
{
if(c.leftRec)
{
esm->read(c.subName.name, 4);
c.leftRec -= 4;
return false;
}
return true;
}
// Skip current sub record, including header (but not including
// name.)
void skipHSub()

@ -6,12 +6,16 @@
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.
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.
TODO: We should handle the actual cell content loading in this file
too, although the solution should be as open as possible and let
the user handle all data storage.
*/
struct Cell
@ -48,7 +52,8 @@ struct Cell
// This is implicit?
//name = esm.getHNString("NAME");
// Ignore this for now, I assume it might mean we delete the entire cell?
// Ignore this for now, it might mean we should delete the entire
// cell?
if(esm.isNextSub("DELE")) esm.skipHSub();
esm.getHNT(data, "DATA", 12);

@ -0,0 +1,247 @@
#ifndef _ESM_INFO_H
#define _ESM_INFO_H
#include "esm_reader.hpp"
#include "defs.hpp"
namespace ESM {
// NOT DONE
/*
* Dialogue information. A series of these follow after DIAL records,
* and form a linked list of dialogue items.
*/
struct DialInfo
{
enum Gender
{
Male = 0,
Female = 1,
NA = -1
};
struct DATAstruct
{
int unknown1;
int disposition;
char rank; // Rank of NPC
char gender; // See Gender enum
char PCrank; // Player rank
char unknown2;
}; // 12 bytes
DATAstruct data;
// The rules for whether or not we will select this dialog item.
struct SelectStruct
{
std::string selectRule; // This has a complicated format
float f; // Only one of 'f' or 'i' is used
int i;
VarType type;
};
// Journal quest indices (introduced with the quest system in Tribunal)
enum QuestStatus
{
QS_None,
QS_Name,
QS_Finished,
QS_Restart,
QS_Deleted
};
// Rules for when to include this item in the final list of options
// visible to the player.
std::vector<SelectStruct> selects;
// Id of this, previous and next INFO items
std::string id, prev, next,
// Various references used in determining when to select this item.
actor, race, clas, npcFaction, pcFaction, cell,
// Sound and text associated with this item
sound, response,
// Result script (uncomiled) to run whenever this dialog item is
// selected
resultScript;
// ONLY include this item the NPC is not part of any faction.
bool factionLess;
// Status of this quest item
QuestStatus questStatus;
// Hexadecimal versions of the various subrecord names.
enum SubNames
{
REC_ONAM = 0x4d414e4f,
REC_RNAM = 0x4d414e52,
REC_CNAM = 0x4d414e43,
REC_FNAM = 0x4d414e46,
REC_ANAM = 0x4d414e41,
REC_DNAM = 0x4d414e44,
REC_SNAM = 0x4d414e53,
REC_NAME = 0x454d414e,
REC_SCVR = 0x52564353,
REC_INTV = 0x56544e49,
REC_FLTV = 0x56544c46,
REC_BNAM = 0x4d414e42,
REC_QSTN = 0x4e545351,
REC_QSTF = 0x46545351,
REC_QSTR = 0x52545351,
REC_DELE = 0x454c4544
};
void load(ESMReader &esm)
{
id = esm.getHNString("INAM");
prev = esm.getHNString("PNAM");
next = esm.getHNString("NNAM");
// Not present if deleted
if(esm.isNextSub("DATA"))
esm.getHT(data, 12);
// What follows is somewhat spaghetti-ish, but it's worth if for
// an extra speedup. INFO is by far the most common record type.
// subName is a reference to the original, so it changes whenever
// a new sub name is read. esm.isEmptyOrGetName() will get the
// next name for us, or return true if there are no more records.
esm.getSubName();
const NAME &subName = esm.retSubName();
if(subName.val == REC_ONAM)
{
actor = esm.getHString();
if(esm.isEmptyOrGetName()) return;
}
if(subName.val == REC_RNAM)
{
race = esm.getHString();
if(esm.isEmptyOrGetName()) return;
}
if(subName.val == REC_CNAM)
{
clas = esm.getHString();
if(esm.isEmptyOrGetName()) return;
}
factionLess = false;
if(subName.val == REC_FNAM)
{
npcFaction = esm.getHString();
if(npcFaction == "FFFF") factionLess = true;
if(esm.isEmptyOrGetName()) return;
}
if(subName.val == REC_ANAM)
{
cell = esm.getHString();
if(esm.isEmptyOrGetName()) return;
}
if(subName.val == REC_DNAM)
{
pcFaction = esm.getHString();
if(esm.isEmptyOrGetName()) return;
}
if(subName.val == REC_SNAM)
{
sound = esm.getHString();
if(esm.isEmptyOrGetName()) return;
}
if(subName.val == REC_NAME)
{
response = esm.getHString();
if(esm.isEmptyOrGetName()) return;
}
while(subName.val == REC_SCVR)
{
SelectStruct ss;
ss.selectRule = esm.getHString();
esm.isEmptyOrGetName();
if(subName.val == REC_INTV)
{
ss.type = VT_Int;
esm.getHT(ss.i);
}
else if(subName.val == REC_FLTV)
{
ss.type = VT_Float;
esm.getHT(ss.f);
}
else
esm.fail("INFO.SCVR must precede INTV or FLTV, not "
+ subName.toString());
selects.push_back(ss);
if(esm.isEmptyOrGetName()) return;
}
if(subName.val == REC_BNAM)
{
resultScript = esm.getHString();
if(esm.isEmptyOrGetName()) return;
}
questStatus = QS_None;
int skip = 1;
if (subName.val == REC_QSTN) questStatus = QS_Name;
else if(subName.val == REC_QSTF) questStatus = QS_Finished;
else if(subName.val == REC_QSTR) questStatus = QS_Restart;
else if(subName.val == REC_DELE) {questStatus = QS_Deleted; skip = 4;}
else
esm.fail("Don't know what to do with " + subName.toString() + " in INFO " + id);
if(questStatus != QS_None)
esm.skip(skip);
}
};
/*
Some old and unused D code and comments, that might be useful later:
--------
// We only need to put each item in ONE list. For if your NPC
// matches this response, then it must match ALL criteria, thus it
// will have to look up itself in all the lists. I think the order
// is well optimized in making the lists as small as possible.
if(this.actor.index != -1) actorDial[this.actor][parent]++;
else if(cell != "") cellDial[cell][parent]++;
else if(this.Class != -1) classDial[this.Class][parent]++;
else if(this.npcFaction != -1)
factionDial[this.npcFaction][parent]++;
else if(this.race != -1) raceDial[this.race][parent]++;
else allDial[parent]++; // Lists dialogues that might
// apply to all npcs.
*/
// List of dialogue topics (and greetings, voices, etc.) that
// reference other objects. Eg. raceDial is indexed by the indices of
// all races referenced. The value of raceDial is a new AA, which is
// basically used as a map (the int value is just a count and isn't
// used for anything important.) The indices (or elements of the map)
// are the dialogues that reference the given race. I use an AA
// instead of a list or array, since each dialogue can be added lots
// of times.
/*
int allDial[Dialogue*];
int classDial[int][Dialogue*];
int factionDial[int][Dialogue*];
int actorDial[Item][Dialogue*];
// If I look up cells on cell load, I don't have to resolve these
// names into anything!
int cellDial[char[]][Dialogue*];
int raceDial[int][Dialogue*];
*/
}
#endif

@ -19,6 +19,7 @@
#include "loadfact.hpp"
#include "loadglob.hpp"
#include "loadgmst.hpp"
#include "loadinfo.hpp"
#include "loadingr.hpp"
#include "loadland.hpp"
#include "loadlevlist.hpp"

@ -1,258 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadinfo.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.loadinfo;
import esm.imports;
import esm.loaddial;
import esm.loadrace;
import esm.loadclas;
import esm.loadfact;
/*
* Dialogue information. These always follow a DIAL record and form a
* linked list
*/
struct DialInfo
{
enum Gender : byte
{
Male = 0,
Female = 1,
NA = -1
}
align(1) struct DATAstruct
{
uint unknown1;
uint disposition;
byte rank; // Rank of NPC
Gender gender; // 0 - male, 1 - female, 0xff - none
byte PCrank; // Player rank
byte unknown2;
static assert(DATAstruct.sizeof==12);
}
DATAstruct data;
// The rules for whether or not we will select this dialog item.
struct SelectStruct
{
char[] selectRule; // This has a complicated format
union { float f; int i; }
VarType type;
}
// Id of prevous and next in linked list
char[] id, prev, next;
// Various references used in determining when to select this item.
Item actor;
Race *race;
Class *cls;
Faction* npcFaction, pcFaction;
bool factionLess; // ONLY select this item the NPC is not part of any faction.
char[] cell; // Use this to compare with current cell name
RegionBuffer!(SelectStruct) selects; // Selection rules
SoundIndex sound; // Sound to play when this is selected.
char[] response; // The text content of this info item.
// TODO: This should probably be compiled at load time?
char[] resultScript; // Result script (uncompiled) - is run if this
// dialog item is selected.
// Journal quest indices (introduced with the quest system in tribunal)
enum Quest
{
None, Name, Finished, Restart
}
bool deleted;
Quest questStatus;
void load(DialogueType tp)
{with(esFile){
id = getHNString("INAM");
prev = getHNString("PNAM");
next = getHNString("NNAM");
// Not present if deleted
if(isNextSub("DATA"))
readHExact(&data, data.sizeof);
// What follows is terrible spaghetti code, but I like it! ;-) In
// all seriousness, it's an attempt to make things faster, since
// INFO is by far the most common record type.
// subName is a slice of the original, so it changes when new sub
// names are read.
getSubName();
char[] subName = retSubName();
if(subName == "ONAM")
{
actor = actors.lookup(tmpHString());
if(isEmptyOrGetName) return;
} else actor.i = null;
if(subName == "RNAM")
{
race = cast(Race*)races.lookup(tmpHString());
if(isEmptyOrGetName) return;
} else race = null;
if(subName == "CNAM")
{
cls = cast(Class*)classes.lookup(tmpHString());
if(isEmptyOrGetName) return;
} else cls = null;
npcFaction = null;
factionLess = false;
if(subName == "FNAM")
{
char[] tmp = tmpHString();
if(tmp == "FFFF") factionLess = true;
else npcFaction = cast(Faction*)factions.lookup(tmp);
if(isEmptyOrGetName) return;
}
if(subName == "ANAM")
{
cell = getHString();
if(isEmptyOrGetName) return;
} else cell = null;
if(subName == "DNAM")
{
pcFaction = cast(Faction*)factions.lookup(tmpHString());
if(isEmptyOrGetName) return;
} else pcFaction = null;
if(subName == "SNAM")
{
sound = resources.lookupSound(tmpHString());
if(isEmptyOrGetName) return;
} else sound = SoundIndex.init;
if(subName == "NAME")
{
response = getHString();
if(isEmptyOrGetName) return;
} else response = null;
selects = null;
while(subName == "SCVR")
{
if(selects is null) selects = esmRegion.getBuffer!(SelectStruct)(1,1);
else selects.length = selects.length + 1;
with(selects.array()[$-1])
{
selectRule = getHString();
isEmptyOrGetName();
if(subName == "INTV") type = VarType.Int;
else if(subName == "FLTV") type = VarType.Float;
else
fail("INFO.SCVR must precede INTV or FLTV, not "
~ subName);
readHExact(&i, 4);
}
if(isEmptyOrGetName) return;
}
if(subName == "BNAM")
{
resultScript = getHString();
if(isEmptyOrGetName) return;
} else resultScript = null;
deleted = false;
questStatus = Quest.None;
if(subName == "QSTN") questStatus = Quest.Name;
else if(subName == "QSTF") questStatus = Quest.Finished;
else if(subName == "QSTR") questStatus = Quest.Restart;
else if(subName == "DELE") {getHInt(); deleted = true;}
else
fail("Don't know what to do with " ~ subName ~ " in INFO " ~ id);
if(questStatus != Quest.None)
{
getHByte();
// The Quest markers should only appear in journal INFOs, but
// sometime the appear outside it. We could issue a warning,
// but lets just ignore it instead.
/*
if(tp != Dialogue.Type.Journal)
writefln("WARNING: Found INFO quest marker in INFO %s type %s",
id, cast(int)tp);
*/
}
}}
}
/*
// We only need to put each item in ONE list. For if your NPC
// matches this response, then it must match ALL criteria, thus it
// will have to look up itself in all the lists. I think the order
// is well optimized in making the lists as small as possible.
if(this.actor.index != -1) actorDial[this.actor][parent]++;
else if(cell != "") cellDial[cell][parent]++;
else if(this.Class != -1) classDial[this.Class][parent]++;
else if(this.npcFaction != -1)
factionDial[this.npcFaction][parent]++;
else if(this.race != -1) raceDial[this.race][parent]++;
else allDial[parent]++; // Lists dialogues that might
// apply to all npcs.
*/
struct DialInfoList
{
LoadState state;
DialInfo d;
void load(DialogueType type)
{
d.load(type);
}
}
// List of dialogue topics (and greetings, voices, etc.) that
// reference other objects. Eg. raceDial is indexed by the indices of
// all races referenced. The value of raceDial is a new AA, which is
// basically used as a map (the int value is just a count and isn't
// used for anything important.) The indices (or elements of the map)
// are the dialogues that reference the given race. I use an AA
// instead of a list or array, since each dialogue can be added lots
// of times.
/*
int allDial[Dialogue*];
int classDial[int][Dialogue*];
int factionDial[int][Dialogue*];
int actorDial[Item][Dialogue*];
// If I look up cells on cell load, I don't have to resolve these
// names into anything!
int cellDial[char[]][Dialogue*];
int raceDial[int][Dialogue*];
*/
Loading…
Cancel
Save