You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw-tes3mp/scene/celldata.d

598 lines
14 KiB
D

/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (celldata.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 scene.celldata;
import std.stdio;
public import esm.esmmain;
import core.memory;
import util.reglist;
import ogre.ogre;
import ogre.bindings;
import sound.audio;
import scene.player;
// Base properties common to all live objects. Currently extremely
// sketchy.
struct LiveObjectBase
{
// Should this stuff be in here?
bool disabled; // Disabled in game
bool deleted; // Deleted relative to plugin file
// Used for objects created in-game, like custom potions or
// enchanted items. These can be completely deleted. It is also used
// for creatures created from leveled lists.
bool transient;
// Is this a door that teleports to another cell?
bool teleport;
// Scale, 1.0 is normal.
float scale;
// Owner of an object / activator
char[] owner;
// A global variable? Don't know what it's used for.
char[] global;
// Reference to a soul trapped creature?
char[] soul;
// Faction owner? Rank?
char[] cnam;
int indx;
// Magic value / health / uses of an item?
float xchg;
// ?? See comment below
int intv, nam9;
// Destination for a door
Placement destPos;
char[] destCell;
// Lock level?
int fltv;
// For locked doors and containers
char[] key, trap;
// Position in 3D world
Placement pos;
// ??
byte unam;
// Don't even get me started on script-related issues
}
// Generic version of a "live" object
struct GenLive(T)
{
// This HAS to be a pointer, since we load the LOB after copying the
// LiveWhatever into the linked list.
LiveObjectBase *base;
T *m;
}
alias GenLive!(Static) LiveStatic;
alias GenLive!(NPC) LiveNPC;
alias GenLive!(Activator) LiveActivator;
alias GenLive!(Potion) LivePotion;
alias GenLive!(Apparatus) LiveApparatus;
alias GenLive!(Ingredient) LiveIngredient;
alias GenLive!(Armor) LiveArmor;
alias GenLive!(Weapon) LiveWeapon;
alias GenLive!(Book) LiveBook;
alias GenLive!(Clothing) LiveClothing;
alias GenLive!(Tool) LiveTool;
alias GenLive!(Creature) LiveCreature;
alias GenLive!(Door) LiveDoor;
alias GenLive!(Misc) LiveMisc;
alias GenLive!(Container) LiveContainer;
struct LiveLight
{
LiveObjectBase *base;
Light *m;
NodePtr lightNode;
SoundInstance *loopSound;
// Lifetime left, in seconds?
float time;
}
class CellData
{
private:
RegionManager reg;
public:
InteriorCell *inCell;
ExteriorCell *exCell;
// Ambient light data
AMBIStruct ambi;
// Water height
int water;
// Linked lists that hold references to all the objects in the cell
RegionList!(LiveStatic) statics;
RegionList!(LiveMisc) miscItems;
RegionList!(LiveLight) lights;
RegionList!(LiveLight) statLights;
RegionList!(LiveNPC) npcs;
RegionList!(LiveContainer) containers;
RegionList!(LiveDoor) doors;
RegionList!(LiveActivator) activators;
RegionList!(LivePotion) potions;
RegionList!(LiveApparatus) appas;
RegionList!(LiveIngredient) ingredients;
RegionList!(LiveArmor) armors;
RegionList!(LiveWeapon) weapons;
RegionList!(LiveBook) books;
RegionList!(LiveTool) tools;
RegionList!(LiveClothing) clothes;
RegionList!(LiveCreature) creatures;
this(RegionManager r)
{
reg = r;
killCell(); // Make sure all data is initialized.
}
// Kills all data and initialize the object for reuse.
void killCell()
{
inCell = null;
exCell = null;
// Reset the lists
statics.init(reg);
miscItems.init(reg);
lights.init(reg);
statLights.init(reg);
npcs.init(reg);
containers.init(reg);
doors.init(reg);
activators.init(reg);
potions.init(reg);
appas.init(reg);
ingredients.init(reg);
armors.init(reg);
weapons.init(reg);
books.init(reg);
tools.init(reg);
clothes.init(reg);
creatures.init(reg);
// Write some statistics
//writefln(reg);
reg.freeAll();
}
// Load an exterior cell
void loadExtCell(int x, int y)
{
exCell = cells.getExt(x, y);
writefln("Name: %s", exCell.name);
writefln("Region: %s", exCell.region.id);
esFile.restoreContext(exCell.context, reg);
// Always for exterior cells
water = 0;
Color mapColor;
if(esFile.isNextSub("NAM5"))
esFile.readHExact(&mapColor, mapColor.sizeof);
loadReferences();
const float cellWidth = 8192;
// TODO/FIXME: This is temporary
playerData.position.position[0] = x*cellWidth;
playerData.position.position[1] = y*cellWidth;
playerData.position.position[2] = 6000;
playerData.position.rotation[] = 0;
}
// Load an interior cell
void loadIntCell(char[] cName)
{
inCell = cells.getInt(cName);
/*
writefln("Cell id: '%s'", cName);
writefln("Cell name: '%s'", cell.id);
*/
esFile.restoreContext(inCell.context, reg);
// TODO: Read this crap in loadcell.d
if(esFile.isNextSub("INTV") || esFile.isNextSub("WHGT"))
water = esFile.getHInt();
//writefln("Water height: ", water);
if(inCell.flags & CellFlags.QuasiExt)
{
Region* reg = esFile.getHNOPtr!(Region)("RGNN", regions);
if(reg) writefln("Cell has region %s", reg.id);
else writefln("No region");
// Determine weather from this region
}
else
{
// Only for interior cells
esFile.readHNExact(&ambi, ambi.sizeof, "AMBI");
}
/*
writefln("Ambient light: ", ambi.ambient.array);
writefln("Sunlight: ", ambi.sunlight.array);
writefln("Fog color: ", ambi.ambient.array);
writefln("Fog density: ", ambi.fogDensity);
*/
loadReferences();
}
private void loadReferences()
{
with(esFile)
{
// Now read all the references
while(hasMoreSubs)
{
// Number of references in the cell? Maximum once in each
// cell, but not always at the beginning, and not always
// right. In other words, completely useless. Strange
// people...
getHNOInt("NAM0", 0);
int refnum = getHNInt("FRMR"); // Reference number
char[] refr = getHNString("NAME"); // ID of object
// Used internally for optimizing
bool container;
bool door;
bool stat;
bool activator;
// Identify the referenced object by looking up the id
// string 'ref' in the global database of all
// cell-referencable objects.
Item itm = cellRefs.lookup(refr);
ItemT it = ItemT(itm);
// Create a new base object that holds all our reference
// data.
LiveObjectBase *base = reg.newT!(LiveObjectBase)();
// These should be ordered according to how commonly they
// occur and how large the reference lists are.
// Static mesh - probably the most common
if(Static *s = it.getStatic())
{
LiveStatic ls;
ls.m = s;
ls.base = base;
statics.insert(ls);
stat = true;
}
// Misc items are also pretty common
else if(Misc *m = it.getMisc())
{
LiveMisc ls;
ls.m = m;
ls.base = base;
miscItems.insert(ls);
}
// Lights and containers too
else if(Light *m = it.getLight())
{
LiveLight ls;
ls.m = m;
ls.base = base;
ls.time = m.data.time;
if(m.data.flags&Light.Flags.Carry)
lights.insert(ls);
else
statLights.insert(ls);
}
else if(Container *c = it.getContainer())
{
LiveContainer ls;
ls.m = c;
ls.base = base;
containers.insert(ls);
container = true;
}
// From here on I doubt the order will matter much
else if(Door *d = it.getDoor())
{
LiveDoor ls;
ls.m = d;
ls.base = base;
doors.insert(ls);
door = true;
}
// Activator?
else if(Activator *a = it.getActivator())
{
LiveActivator ls;
ls.m = a;
ls.base = base;
activators.insert(ls);
activator = true;
}
// NPC?
else if(NPC *n = it.getNPC())
{
LiveNPC ls;
ls.m = n;
ls.base = base;
npcs.insert(ls);
}
else if(Potion *p = it.getPotion())
{
LivePotion ls;
ls.m = p;
ls.base = base;
potions.insert(ls);
}
else if(Apparatus *m = it.getApparatus())
{
LiveApparatus ls;
ls.m = m;
ls.base = base;
appas.insert(ls);
}
else if(Ingredient *m = it.getIngredient())
{
LiveIngredient ls;
ls.m = m;
ls.base = base;
ingredients.insert(ls);
}
else if(Armor *m = it.getArmor())
{
LiveArmor ls;
ls.m = m;
ls.base = base;
armors.insert(ls);
}
else if(Weapon *m = it.getWeapon())
{
LiveWeapon ls;
ls.m = m;
ls.base = base;
weapons.insert(ls);
}
else if(Book *m = it.getBook())
{
LiveBook ls;
ls.m = m;
ls.base = base;
books.insert(ls);
}
else if(Clothing *m = it.getClothing())
{
LiveClothing ls;
ls.m = m;
ls.base = base;
clothes.insert(ls);
}
else if(Tool *m = it.getPick())
{
LiveTool ls;
ls.m = m;
ls.base = base;
tools.insert(ls);
}
else if(Tool *m = it.getProbe())
{
LiveTool ls;
ls.m = m;
ls.base = base;
tools.insert(ls);
}
else if(Tool *m = it.getRepair())
{
LiveTool ls;
ls.m = m;
ls.base = base;
tools.insert(ls);
}
else if(Creature *c = it.getCreature())
{
LiveCreature ls;
ls.m = c;
ls.base = base;
creatures.insert(ls);
}
else if(LeveledCreatures *l = it.getCreatureList)
{
// Create a creature, based on current player level.
LiveCreature ls;
ls.m = l.instCreature(playerData.level);
if(ls.m != null)
{
ls.base = base;
creatures.insert(ls);
}
}
else fail(format(" UNKNOWN REFERENCE! Type ", cast(int)it.i.type));
// Now that the object has found it's place, load data
// into base.
with(*base)
{
// ALL variables must be initialized here
disabled = false;
deleted = false;
transient = false;
teleport = false;
// Scale
scale = getHNOFloat("XSCL", 1.0);
// Statics only need the position data. Skip the
// unneeded calls to isNextSub() as an optimization.
if(stat) goto readpos;
// An NPC that owns this object (and will get angry if
// you steal it)
owner = getHNOString("ANAM");
// ??? I have no idea, link to a global variable
global = getHNOString("BNAM");
// ID of creature trapped in a soul gem (?)
soul = getHNOString("XSOL");
// ?? CNAM has a faction name, might be for
// objects/beds etc belonging to a faction.
cnam = getHNOString("CNAM");
// INDX might be PC faction rank required to use the
// item? Sometimes is -1.
if(cnam.length) indx = getHNInt("INDX");
// Possibly weapon health, number of uses left or
// weapon magic charge?
xchg = getHNOFloat("XCHG", 0.0);
// I have no idea, these are present some times, often
// along with owner (ANAM) and sometimes otherwise. Is
// NAM9 is always 1? INTV is usually one, but big for
// lights. Perhaps something to do with remaining
// light "charge". I haven't tried reading it as a
// float in those cases.
intv = getHNOInt("INTV", 0);
nam9 = getHNOInt("NAM9", 0);
// Present for doors that teleport you to another
// cell.
if(door && isNextSub("DODT"))
{
teleport = true;
readHExact(&destPos, destPos.sizeof);
// Destination cell (optitional?)
destCell = getHNOString("DNAM");
}
if(door || container)
{
// Lock level (I think)
fltv = getHNOInt("FLTV", 0);
// For locked doors and containers
key = getHNOString("KNAM");
trap = getHNOString("TNAM");
}
if(activator)
{
// Occurs ONCE in Morrowind.esm, for an activator.
unam = getHNOByte("UNAM", 0);
// Occurs in Tribunal.esm, eg. in the cell
// "Mournhold, Plaza Brindisi Dorom", where it has
// the value 100.
fltv = getHNOInt("FLTV", 0);
}
readpos:
// Position of this object within the cell
readHNExact(&pos, Placement.sizeof, "DATA");
// TODO/FIXME: Very temporary. Set player position at
// the first door we find.
if(door && !playerData.posSet)
{
playerData.posSet = true;
playerData.position = pos;
}
}
}
// Skip this? Large chance that the same file will be used for
// the next cell load, and the rest of our system can handle
// that without closing or reopening the file.
//close(); // Close the file
}
}
}
CellFreelist cellList;
// Structure used as a free list for cell data objects and their
// respective regions.
struct CellFreelist
{
// We love static arrays! And 100 cells should be enough for
// everybody :)
CellData[100] list;
uint next;
// TODO: Figure out a good size to use here as well.
CellData get()
{
if(next) return list[--next];
// Since these are reused, there's no waste in allocating on the
// stack here. Also, this is only semi-runtime (executed when
// loading a cell), thus a rare GC slow down is non-critical.
return new CellData(new RegionManager("CELL"));
}
void release(CellData r)
{
assert(next < list.length);
r.killCell();
list[next++] = r;
}
}