Data resource management (maps)

This commit is contained in:
Justin Aquadro 2011-11-09 21:46:19 -05:00
parent d9230b0d31
commit 3b781d0a02
14 changed files with 1398 additions and 9 deletions

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using Substrate.Core;
using Substrate.Nbt;
using Substrate.Data;
//TODO: Exceptions (+ Alpha)
@ -26,6 +27,7 @@ namespace Substrate
private Dictionary<int, BlockManager> _blockMgrs;
private PlayerManager _playerMan;
private BetaDataManager _dataMan;
private int _prefCacheSize = 256;
@ -129,6 +131,15 @@ namespace Substrate
return GetPlayerManagerVirt() as PlayerManager;
}
/// <summary>
/// Gets a <see cref="BetaDataManager"/> for managing data resources, such as maps.
/// </summary>
/// <returns>A <see cref="BetaDataManager"/> for this world.</returns>
public new BetaDataManager GetDataManager ()
{
return GetDataManagerVirt() as BetaDataManager;
}
/// <summary>
/// Saves the world's <see cref="Level"/> data, and any <see cref="Chunk"/> objects known to have unsaved changes.
/// </summary>
@ -157,7 +168,7 @@ namespace Substrate
/// <param name="path">The path to the directory containing the world's level.dat, or the path to level.dat itself.</param>
/// <param name="cacheSize">The preferred cache size in chunks for each opened dimension in this world.</param>
/// <returns>A new <see cref="BetaWorld"/> object representing an existing world on disk.</returns>
public static new BetaWorld Open (string path, int cacheSize)
public static BetaWorld Open (string path, int cacheSize)
{
BetaWorld world = new BetaWorld().OpenWorld(path);
world._prefCacheSize = cacheSize;
@ -230,6 +241,17 @@ namespace Substrate
return _playerMan;
}
/// <exclude/>
protected override Data.DataManager GetDataManagerVirt ()
{
if (_dataMan != null) {
return _dataMan;
}
_dataMan = new BetaDataManager(this);
return _dataMan;
}
private void OpenDimension (int dim)
{
string path = Path;

View file

@ -7,6 +7,14 @@ using Substrate.Nbt;
namespace Substrate.Core
{
public enum CompressionType
{
None,
Zlib,
Deflate,
GZip,
}
public class NBTFile
{
private string _filename;
@ -37,7 +45,12 @@ namespace Substrate.Core
return Timestamp(File.GetLastWriteTime(_filename));
}
public virtual Stream GetDataInputStream ()
public Stream GetDataInputStream ()
{
return GetDataInputStream(CompressionType.GZip);
}
public virtual Stream GetDataInputStream (CompressionType compression)
{
try {
FileStream fstr = new FileStream(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
@ -50,17 +63,44 @@ namespace Substrate.Core
fstr.Close();
return new GZipStream(new MemoryStream(data), CompressionMode.Decompress);
switch (compression) {
case CompressionType.None:
return new MemoryStream(data);
case CompressionType.GZip:
return new GZipStream(new MemoryStream(data), CompressionMode.Decompress);
case CompressionType.Zlib:
return new ZlibStream(new MemoryStream(data), CompressionMode.Decompress);
case CompressionType.Deflate:
return new DeflateStream(new MemoryStream(data), CompressionMode.Decompress);
default:
throw new ArgumentException("Invalid CompressionType specified", "compression");
}
}
catch (Exception ex) {
throw new NbtIOException("Failed to open compressed NBT data stream for input.", ex);
}
}
public virtual Stream GetDataOutputStream ()
public Stream GetDataOutputStream ()
{
return GetDataOutputStream(CompressionType.GZip);
}
public virtual Stream GetDataOutputStream (CompressionType compression)
{
try {
return new GZipStream(new NBTBuffer(this), CompressionMode.Compress);
switch (compression) {
case CompressionType.None:
return new NBTBuffer(this);
case CompressionType.GZip:
return new GZipStream(new NBTBuffer(this), CompressionMode.Compress);
case CompressionType.Zlib:
return new ZlibStream(new NBTBuffer(this), CompressionMode.Compress);
case CompressionType.Deflate:
return new DeflateStream(new NBTBuffer(this), CompressionMode.Compress);
default:
throw new ArgumentException("Invalid CompressionType specified", "compression");
}
}
catch (Exception ex) {
throw new NbtIOException("Failed to initialize compressed NBT data stream for output.", ex);

View file

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using Substrate.Nbt;
using System.IO;
using Substrate.Core;
namespace Substrate.Data
{
public class BetaDataManager : DataManager, INbtObject<BetaDataManager>
{
private static SchemaNodeCompound _schema = new SchemaNodeCompound()
{
new SchemaNodeScaler("map", TagType.TAG_SHORT),
};
private TagNodeCompound _source;
private NbtWorld _world;
private short _mapId;
private MapManager _maps;
public BetaDataManager (NbtWorld world)
{
_world = world;
_maps = new MapManager(_world);
}
public override int CurrentMapId
{
get { return _mapId; }
set { _mapId = (short)value; }
}
public new MapManager Maps
{
get { return _maps; }
}
protected override IMapManager GetMapManager ()
{
return _maps;
}
public bool Save ()
{
if (_world == null) {
return false;
}
try {
string path = Path.Combine(_world.Path, _world.DataDirectory);
NBTFile nf = new NBTFile(Path.Combine(path, "idcounts.dat"));
Stream zipstr = nf.GetDataOutputStream(CompressionType.None);
if (zipstr == null) {
NbtIOException nex = new NbtIOException("Failed to initialize uncompressed NBT stream for output");
nex.Data["DataManager"] = this;
throw nex;
}
new NbtTree(BuildTree() as TagNodeCompound).WriteTo(zipstr);
zipstr.Close();
return true;
}
catch (Exception ex) {
Exception lex = new Exception("Could not save idcounts.dat file.", ex);
lex.Data["DataManager"] = this;
throw lex;
}
}
#region INBTObject<DataManager>
public virtual BetaDataManager LoadTree (TagNode tree)
{
TagNodeCompound ctree = tree as TagNodeCompound;
if (ctree == null) {
return null;
}
_mapId = ctree["map"].ToTagShort();
_source = ctree.Copy() as TagNodeCompound;
return this;
}
public virtual BetaDataManager LoadTreeSafe (TagNode tree)
{
if (!ValidateTree(tree)) {
return null;
}
return LoadTree(tree);
}
public virtual TagNode BuildTree ()
{
TagNodeCompound tree = new TagNodeCompound();
tree["map"] = new TagNodeLong(_mapId);
if (_source != null) {
tree.MergeFrom(_source);
}
return tree;
}
public virtual bool ValidateTree (TagNode tree)
{
return new NbtVerifier(tree, _schema).Verify();
}
#endregion
}
}

View file

@ -0,0 +1,46 @@
using System;
using System.Runtime.Serialization;
namespace Substrate.Data
{
/// <summary>
/// The exception that is thrown when IO errors occur during high-level data resource management operations.
/// </summary>
[Serializable]
public class DataIOException : SubstrateException
{
/// <summary>
/// Initializes a new instance of the <see cref="DataIOException"/> class.
/// </summary>
public DataIOException ()
: base()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DataIOException"/> class with a custom error message.
/// </summary>
/// <param name="message">A custom error message.</param>
public DataIOException (string message)
: base(message)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DataIOException"/> class with a custom error message and a reference to
/// an InnerException representing the original cause of the exception.
/// </summary>
/// <param name="message">A custom error message.</param>
/// <param name="innerException">A reference to the original exception that caused the error.</param>
public DataIOException (string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DataIOException"/> class with serialized data.
/// </summary>
/// <param name="info">The object that holds the serialized object data.</param>
/// <param name="context">The contextual information about the source or destination.</param>
protected DataIOException (SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
namespace Substrate.Data
{
/// <summary>
/// Provides a common interface for managing additional data resources in a world.
/// </summary>
public abstract class DataManager
{
/// <summary>
/// Gets or sets the id of the next map to be created.
/// </summary>
public virtual int CurrentMapId
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
/// <summary>
/// Gets an <see cref="IMapManager"/> for managing <see cref="Map"/> data resources.
/// </summary>
public IMapManager Maps
{
get { return GetMapManager(); }
}
/// <summary>
/// Gets an <see cref="IMapManager"/> for managing <see cref="Map"/> data resources.
/// </summary>
/// <returns>An <see cref="IMapManager"/> instance appropriate for the concrete <see cref="DataManager"/> instance.</returns>
protected virtual IMapManager GetMapManager ()
{
return null;
}
/// <summary>
/// Saves any metadata required by the world for managing data resources.
/// </summary>
/// <returns><c>true</c> on success, or <c>false</c> if data could not be saved.</returns>
public bool Save ()
{
return true;
}
}
}

View file

@ -0,0 +1,342 @@
using System;
using System.Collections.Generic;
using System.Text;
using Substrate.Core;
using Substrate.Nbt;
using System.IO;
namespace Substrate.Data
{
/// <summary>
/// Represents the complete data of a Map item.
/// </summary>
public class Map : INbtObject<Map>, ICopyable<Map>
{
private static SchemaNodeCompound _schema = new SchemaNodeCompound()
{
new SchemaNodeCompound("data")
{
new SchemaNodeScaler("scale", TagType.TAG_BYTE),
new SchemaNodeScaler("dimension", TagType.TAG_BYTE),
new SchemaNodeScaler("height", TagType.TAG_SHORT),
new SchemaNodeScaler("width", TagType.TAG_SHORT),
new SchemaNodeScaler("xCenter", TagType.TAG_INT),
new SchemaNodeScaler("zCenter", TagType.TAG_INT),
new SchemaNodeArray("colors"),
},
};
private TagNodeCompound _source;
private NbtWorld _world;
private int _id;
private byte _scale;
private byte _dimension;
private short _height;
private short _width;
private int _x;
private int _z;
private byte[] _colors;
/// <summary>
/// Creates a new default <see cref="Map"/> object.
/// </summary>
public Map ()
{
_scale = 3;
_dimension = 0;
_height = 128;
_width = 128;
_colors = new byte[_width * _height];
}
/// <summary>
/// Creates a new <see cref="Map"/> object with copied data.
/// </summary>
/// <param name="p">A <see cref="Map"/> to copy data from.</param>
protected Map (Map p)
{
_world = p._world;
_id = p._id;
_scale = p._scale;
_dimension = p._dimension;
_height = p._height;
_width = p._width;
_x = p._x;
_z = p._z;
_colors = new byte[_width * _height];
if (p._colors != null) {
p._colors.CopyTo(_colors, 0);
}
}
/// <summary>
/// Gets or sets the id value associated with this map.
/// </summary>
public int Id
{
get { return _id; }
set
{
if (_id < 0 || _id >= 65536) {
throw new ArgumentOutOfRangeException("value", value, "Map Ids must be in the range [0, 65535].");
}
_id = value;
}
}
/// <summary>
/// Gets or sets the scale of the map. Acceptable values are 0 (1:1) to 4 (1:16).
/// </summary>
public int Scale
{
get { return _scale; }
set { _scale = (byte)value; }
}
/// <summary>
/// Gets or sets the (World) Dimension of the map.
/// </summary>
public int Dimension
{
get { return _dimension; }
set { _dimension = (byte)value; }
}
/// <summary>
/// Gets or sets the height of the map.
/// </summary>
/// <remarks>If the new height dimension is different, the map's color data will be reset.</remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the new height value is zero or negative.</exception>
public int Height
{
get { return _height; }
set
{
if (value <= 0) {
throw new ArgumentOutOfRangeException("value", "Height must be a positive number");
}
if (_height != value) {
_height = (short)value;
_colors = new byte[_width * _height];
}
}
}
/// <summary>
/// Gets or sets the width of the map.
/// </summary>
/// <remarks>If the new width dimension is different, the map's color data will be reset.</remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the new width value is zero or negative.</exception>
public int Width
{
get { return _width; }
set
{
if (value <= 0) {
throw new ArgumentOutOfRangeException("value", "Width must be a positive number");
}
if (_width != value) {
_width = (short)value;
_colors = new byte[_width * _height];
}
}
}
/// <summary>
/// Gets or sets the global X-coordinate that this map is centered on, in blocks.
/// </summary>
public int X
{
get { return _x; }
set { _x = value; }
}
/// <summary>
/// Gets or sets the global Z-coordinate that this map is centered on, in blocks.
/// </summary>
public int Z
{
get { return _z; }
set { _z = value; }
}
/// <summary>
/// Gets the raw byte array of the map's color index values.
/// </summary>
public byte[] Colors
{
get { return _colors; }
}
/// <summary>
/// Gets or sets a color index value within the map's internal colors bitmap.
/// </summary>
/// <param name="x">The X-coordinate to get or set.</param>
/// <param name="z">The Z-coordinate to get or set.</param>
/// <exception cref="IndexOutOfRangeException">Thrown when the X- or Z-coordinates exceed the map dimensions.</exception>
public byte this[int x, int z]
{
get
{
if (x < 0 || x >= _width || z < 0 || z >= _height) {
throw new IndexOutOfRangeException();
}
return _colors[x + _width * z];
}
set
{
if (x < 0 || x >= _width || z < 0 || z >= _height) {
throw new IndexOutOfRangeException();
}
_colors[x + _width * z] = value;
}
}
/// <summary>
/// Saves a <see cref="Map"/> object to disk as a standard compressed NBT stream.
/// </summary>
/// <returns>True if the map was saved; false otherwise.</returns>
/// <exception cref="Exception">Thrown when an error is encountered writing out the level.</exception>
public bool Save ()
{
if (_world == null) {
return false;
}
try {
string path = Path.Combine(_world.Path, _world.DataDirectory);
NBTFile nf = new NBTFile(Path.Combine(path, "map_" + _id + ".dat"));
Stream zipstr = nf.GetDataOutputStream();
if (zipstr == null) {
NbtIOException nex = new NbtIOException("Failed to initialize compressed NBT stream for output");
nex.Data["Map"] = this;
throw nex;
}
new NbtTree(BuildTree() as TagNodeCompound).WriteTo(zipstr);
zipstr.Close();
return true;
}
catch (Exception ex) {
Exception mex = new Exception("Could not save map file.", ex); // TODO: Exception Type
mex.Data["Map"] = this;
throw mex;
}
}
#region INBTObject<Map> Members
/// <summary>
/// Attempt to load a Map subtree into the <see cref="Map"/> without validation.
/// </summary>
/// <param name="tree">The root node of a Map subtree.</param>
/// <returns>The <see cref="Map"/> returns itself on success, or null if the tree was unparsable.</returns>
public virtual Map LoadTree (TagNode tree)
{
TagNodeCompound dtree = tree as TagNodeCompound;
if (dtree == null) {
return null;
}
TagNodeCompound ctree = dtree["data"].ToTagCompound();
_scale = ctree["scale"].ToTagByte();
_dimension = ctree["dimension"].ToTagByte();
_height = ctree["height"].ToTagShort();
_width = ctree["width"].ToTagShort();
_x = ctree["xCenter"].ToTagInt();
_z = ctree["zCenter"].ToTagInt();
_colors = ctree["colors"].ToTagByteArray();
_source = ctree.Copy() as TagNodeCompound;
return this;
}
/// <summary>
/// Attempt to load a Map subtree into the <see cref="Map"/> with validation.
/// </summary>
/// <param name="tree">The root node of a Map subtree.</param>
/// <returns>The <see cref="Map"/> returns itself on success, or null if the tree failed validation.</returns>
public virtual Map LoadTreeSafe (TagNode tree)
{
if (!ValidateTree(tree)) {
return null;
}
Map map = LoadTree(tree);
if (map != null) {
if (map._colors.Length != map._width * map._height) {
throw new Exception("Unexpected length of colors byte array in Map"); // TODO: Expception Type
}
}
return map;
}
/// <summary>
/// Builds a Map subtree from the current data.
/// </summary>
/// <returns>The root node of a Map subtree representing the current data.</returns>
public virtual TagNode BuildTree ()
{
TagNodeCompound data = new TagNodeCompound();
data["scale"] = new TagNodeByte(_scale);
data["dimension"] = new TagNodeByte(_dimension);
data["height"] = new TagNodeShort(_height);
data["width"] = new TagNodeShort(_width);
data["xCenter"] = new TagNodeInt(_x);
data["zCenter"] = new TagNodeInt(_z);
data["colors"] = new TagNodeByteArray(_colors);
if (_source != null) {
data.MergeFrom(_source);
}
TagNodeCompound tree = new TagNodeCompound();
tree.Add("data", data);
return tree;
}
/// <summary>
/// Validate a Map subtree against a schema defintion.
/// </summary>
/// <param name="tree">The root node of a Map subtree.</param>
/// <returns>Status indicating whether the tree was valid against the internal schema.</returns>
public virtual bool ValidateTree (TagNode tree)
{
return new NbtVerifier(tree, _schema).Verify();
}
#endregion
#region ICopyable<Map> Members
/// <summary>
/// Creates a deep-copy of the <see cref="Map"/>.
/// </summary>
/// <returns>A deep-copy of the <see cref="Map"/>.</returns>
public virtual Map Copy ()
{
return new Map(this);
}
#endregion
}
}

View file

@ -0,0 +1,464 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
namespace Substrate.Data
{
/// <summary>
/// Represents a range of color index values that pertains to an individual group of blocks.
/// </summary>
public enum ColorGroup
{
Unexplored = 0,
Grass = 4,
Sand = 8,
Other = 12,
Lava = 16,
Ice = 20,
Iron = 24,
Leaves = 28,
Snow = 32,
Clay = 36,
Dirt = 40,
Stone = 44,
Water = 48,
Wood = 52,
}
/// <summary>
/// A utility class for converting <see cref="Map"/> colors and data.
/// </summary>
public class MapConverter
{
private static Color[] _defaultColorIndex;
private Color[] _colorIndex;
private ColorGroup[] _blockIndex;
private int _groupSize = 4;
private Vector3[] _labIndex;
/// <summary>
/// Creates a new <see cref="MapConverter"/> with a Minecraft-default color-index and block-index.
/// </summary>
public MapConverter ()
{
_colorIndex = new Color[256];
_labIndex = new Vector3[256];
_defaultColorIndex.CopyTo(_colorIndex, 0);
RefreshColorCache();
// Setup default block index
_blockIndex = new ColorGroup[256];
for (int i = 0; i < 256; i++) {
_blockIndex[i] = ColorGroup.Other;
}
_blockIndex[BlockInfo.Grass.ID] = ColorGroup.Grass;
_blockIndex[BlockInfo.TallGrass.ID] = ColorGroup.Grass;
_blockIndex[BlockInfo.Sand.ID] = ColorGroup.Sand;
_blockIndex[BlockInfo.Gravel.ID] = ColorGroup.Sand;
_blockIndex[BlockInfo.Sandstone.ID] = ColorGroup.Sand;
_blockIndex[BlockInfo.Lava.ID] = ColorGroup.Lava;
_blockIndex[BlockInfo.StationaryLava.ID] = ColorGroup.Lava;
_blockIndex[BlockInfo.Ice.ID] = ColorGroup.Ice;
_blockIndex[BlockInfo.Leaves.ID] = ColorGroup.Leaves;
_blockIndex[BlockInfo.YellowFlower.ID] = ColorGroup.Leaves;
_blockIndex[BlockInfo.RedRose.ID] = ColorGroup.Leaves;
_blockIndex[BlockInfo.Snow.ID] = ColorGroup.Snow;
_blockIndex[BlockInfo.SnowBlock.ID] = ColorGroup.Snow;
_blockIndex[BlockInfo.ClayBlock.ID] = ColorGroup.Clay;
_blockIndex[BlockInfo.Dirt.ID] = ColorGroup.Dirt;
_blockIndex[BlockInfo.Stone.ID] = ColorGroup.Stone;
_blockIndex[BlockInfo.Cobblestone.ID] = ColorGroup.Stone;
_blockIndex[BlockInfo.CoalOre.ID] = ColorGroup.Stone;
_blockIndex[BlockInfo.IronOre.ID] = ColorGroup.Stone;
_blockIndex[BlockInfo.GoldOre.ID] = ColorGroup.Stone;
_blockIndex[BlockInfo.DiamondOre.ID] = ColorGroup.Stone;
_blockIndex[BlockInfo.RedstoneOre.ID] = ColorGroup.Stone;
_blockIndex[BlockInfo.LapisOre.ID] = ColorGroup.Stone;
_blockIndex[BlockInfo.Water.ID] = ColorGroup.Water;
_blockIndex[BlockInfo.StationaryWater.ID] = ColorGroup.Water;
_blockIndex[BlockInfo.Wood.ID] = ColorGroup.Wood;
}
/// <summary>
/// Gets or sets the number of color levels within each color group. The Minecraft default is 4.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the property is assigned a non-positive value.</exception>
public int ColorGroupSize
{
get { return _groupSize; }
set
{
if (value <= 0) {
throw new ArgumentOutOfRangeException("The ColorGroupSize property must be a positive number.");
}
_groupSize = value;
}
}
/// <summary>
/// Gets the color index table, used to translate map color index values to RGB color values.
/// </summary>
public Color[] ColorIndex
{
get { return _colorIndex; }
}
/// <summary>
/// Gets the block index table, used to translate block IDs to <see cref="ColorGroup"/>s that represent them.
/// </summary>
public ColorGroup[] BlockIndex
{
get { return _blockIndex; }
}
/// <summary>
/// Returns the baseline color index associated with the <see cref="ColorGroup"/> currently representing the given block ID.
/// </summary>
/// <param name="blockId">The ID of a block.</param>
/// <returns>A color index value.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="blockId"/> is out of its normal range.</exception>
public int BlockToColorIndex (int blockId)
{
return BlockToColorIndex(blockId, 0);
}
/// <summary>
/// Returns a color index associated with the <see cref="ColorGroup"/> currently representing the given block ID and based on the given level.
/// </summary>
/// <param name="blockId">The ID of a block.</param>
/// <param name="level">The color level to select from within the derived <see cref="ColorGroup"/>.</param>
/// <returns>A color index value.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when either <paramref name="blockId"/> or <paramref name="level"/> are out of their normal ranges.</exception>
public int BlockToColorIndex (int blockId, int level) {
if (level < 0 || level >= _groupSize) {
throw new ArgumentOutOfRangeException("level", level, "Argument 'level' must be in range [0, " + (_groupSize - 1) + "]");
}
if (blockId < 0 || blockId >= 256) {
throw new ArgumentOutOfRangeException("blockId");
}
return (int)_blockIndex[blockId] * _groupSize + level;
}
/// <summary>
/// Returns a <see cref="Color"/> value assocated with the <see cref="ColorGroup"/> currently representing the given block ID.
/// </summary>
/// <param name="blockId">The ID of a block.</param>
/// <returns>A <see cref="Color"/> value.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="blockId"/> is out of its normal range.</exception>
/// <exception cref="InvalidOperationException">Thrown when <paramref name="blockId"/> maps to an invalid color index.</exception>
public Color BlockToColor (int blockId)
{
return BlockToColor(blockId, 0);
}
/// <summary>
/// Returns a <see cref="Color"/> value associated with the <see cref="ColorGroup"/> currently representing the given block ID and based on the given level.
/// </summary>
/// <param name="blockId">The ID of a block.</param>
/// <param name="level">The color level to select from within the derived <see cref="ColorGroup"/>.</param>
/// <returns>A <see cref="Color"/> value.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when either <paramref name="blockId"/> or <paramref name="level"/> are out of their normal ranges.</exception>
/// <exception cref="InvalidOperationException">Thrown when <paramref name="blockId"/> maps to an invalid color index.</exception>
public Color BlockToColor (int blockId, int level)
{
int ci = BlockToColorIndex(blockId, level);
if (ci < 0 || ci >= 256) {
throw new InvalidOperationException("The specified Block ID mapped to an invalid color index.");
}
return _colorIndex[ci];
}
/// <summary>
/// Returns the <see cref="ColorGroup"/> that a particular color index is part of.
/// </summary>
/// <param name="colorIndex">A color index value.</param>
/// <returns>A <see cref="ColorGroup"/> value.</returns>
public ColorGroup ColorIndexToGroup (int colorIndex)
{
int group = colorIndex / _groupSize;
try {
return (ColorGroup)group;
}
catch {
return ColorGroup.Other;
}
}
/// <summary>
/// Returns the baseline color index within a particular <see cref="ColorGroup"/>.
/// </summary>
/// <param name="group">A <see cref="ColorGroup"/> value.</param>
/// <returns>The baseline (level = 0) color index value for the given <see cref="ColorGroup"/>.</returns>
public int GroupToColorIndex (ColorGroup group)
{
return GroupToColorIndex(group, 0);
}
/// <summary>
/// Returns the color index for a given <see cref="ColorGroup"/> and group level.
/// </summary>
/// <param name="group">A <see cref="ColorGroup"/> value.</param>
/// <param name="level">A level value within the <see cref="ColorGroup"/>.</param>
/// <returns>The color index value for the given <see cref="ColorGroup"/> and group level.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="level"/> is out of range with respect to the current <see cref="ColorGroupSize"/> parameter.</exception>
public int GroupToColorIndex (ColorGroup group, int level)
{
if (level < 0 || level >= _groupSize) {
throw new ArgumentOutOfRangeException("level", level, "Argument 'level' must be in range [0, " + (_groupSize - 1) + "]");
}
return (int)group * _groupSize + level;
}
/// <summary>
/// Returns the baseline <see cref="Color"/> within a particular <see cref="ColorGroup"/>.
/// </summary>
/// <param name="group">A <see cref="ColorGroup"/> value.</param>
/// <returns>The baseline (level = 0) <see cref="Color"/> for the given <see cref="ColorGroup"/>.</returns>
public Color GroupToColor (ColorGroup group)
{
return GroupToColor(group, 0);
}
/// <summary>
/// Returns the <see cref="Color"/> for a given <see cref="ColorGroup"/> and group level.
/// </summary>
/// <param name="group">A <see cref="ColorGroup"/> value.</param>
/// <param name="level">A level value within the <see cref="ColorGroup"/> and group level.</param>
/// <returns>The <see cref="Color"/> for the given <see cref="ColorGroup"/> and group level.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="level"/> is out of range with respect to the current <see cref="ColorGroupSize"/> parameter.</exception>
/// <exception cref="InvalidOperationException">Thrown when the <paramref name="group"/> and <paramref name="level"/> map to an invalid color index.</exception>
public Color GroupToColor (ColorGroup group, int level)
{
int ci = GroupToColorIndex(group, level);
if (ci < 0 || ci >= 256) {
throw new InvalidOperationException("The specified group mapped to an invalid color index.");
}
return _colorIndex[ci];
}
/// <summary>
/// Rebuilds the internal color conversion tables. Should be called after modifying the <see cref="ColorIndex"/> table.
/// </summary>
public void RefreshColorCache ()
{
for (int i = 0; i < _colorIndex.Length; i++) {
_labIndex[i] = RgbToLab(_colorIndex[i]);
}
}
/// <summary>
/// Given a <see cref="Color"/>, returns the index of the closest matching color from the color index table.
/// </summary>
/// <param name="color">The source <see cref="Color"/>.</param>
/// <returns>The closest matching color index value.</returns>
/// <remarks>This method performs color comparisons in the CIELAB color space, to find the best match according to human perception.</remarks>
public int NearestColorIndex (Color color)
{
double min = double.MaxValue;
int minIndex = 0;
Vector3 cr = RgbToLab(color);
for (int i = 0; i < _colorIndex.Length; i++) {
if (_colorIndex[i].A == 0) {
continue;
}
double x = cr.X - _labIndex[i].X;
double y = cr.Y - _labIndex[i].Y;
double z = cr.Z - _labIndex[i].Z;
double err = x * x + y * y + z * z;
if (err < min) {
min = err;
minIndex = i;
}
}
return minIndex;
}
/// <summary>
/// Given a <see cref="Color"/>, returns the cloest matching <see cref="Color"/> from the color index table.
/// </summary>
/// <param name="color">The source <see cref="Color"/>.</param>
/// <returns>The closest matching <see cref="Color"/>.</returns>
/// <remarks>This method performs color comparisons in the CIELAB color space, to find the best match according to human perception.</remarks>
public Color NearestColor (Color color)
{
return _colorIndex[NearestColorIndex(color)];
}
/// <summary>
/// Fills a <see cref="Map"/>'s color data using nearest-matching colors from a source <see cref="Bitmap"/>.
/// </summary>
/// <param name="map">The <see cref="Map"/> to modify.</param>
/// <param name="bmp">The source <see cref="Bitmap"/>.</param>
/// <exception cref="InvalidOperationException">Thrown when the <paramref name="map"/> and <paramref name="bmp"/> objects have different dimensions.</exception>
public void BitmapToMap (Map map, Bitmap bmp)
{
if (map.Width != bmp.Width || map.Height != bmp.Height) {
throw new InvalidOperationException("The source map and bitmap must have the same dimensions.");
}
for (int x = 0; x < map.Width; x++) {
for (int z = 0; z < map.Height; z++) {
Color c = bmp.GetPixel(x, z);
map[x, z] = (byte)NearestColorIndex(c);
}
}
}
/// <summary>
/// Creates a 32bpp <see cref="Bitmap"/> from a <see cref="Map"/>.
/// </summary>
/// <param name="map">The source <see cref="Map"/> object.</param>
/// <returns>A 32bpp <see cref="Bitmap"/> with the same dimensions and pixel data as the source <see cref="Map"/>.</returns>
public Bitmap MapToBitmap (Map map)
{
Bitmap bmp = new Bitmap(map.Width, map.Height, PixelFormat.Format32bppArgb);
for (int x = 0; x < map.Width; x++) {
for (int z = 0; z < map.Height; z++) {
Color c = _colorIndex[map[x, z]];
bmp.SetPixel(x, z, c);
}
}
return bmp;
}
private Vector3 RgbToXyz (Color color)
{
double r = color.R / 255.0;
double g = color.G / 255.0;
double b = color.B / 255.0;
r = (r > 0.04045)
? Math.Pow((r + 0.055) / 1.055, 2.4)
: r / 12.92;
g = (g > 0.04045)
? Math.Pow((g + 0.055) / 1.055, 2.4)
: g / 12.92;
b = (b > 0.04045)
? Math.Pow((b + 0.055) / 1.055, 2.4)
: b / 12.92;
r *= 100;
g *= 100;
b *= 100;
Vector3 xyz = new Vector3();
xyz.X = r * 0.4124 + g * 0.3576 + b * 0.1805;
xyz.Y = r * 0.2126 + g * 0.7152 + b * 0.0722;
xyz.Z = r * 0.0193 + g * 0.1192 + b * 0.9505;
return xyz;
}
private Vector3 XyzToLab (Vector3 xyz)
{
double x = xyz.X / 95.047;
double y = xyz.Y / 100.0;
double z = xyz.Z / 108.883;
x = (x > 0.008856)
? Math.Pow(x, 1.0 / 3.0)
: (7.787 * x) + (16.0 / 116.0);
y = (y > 0.008856)
? Math.Pow(y, 1.0 / 3.0)
: (7.787 * y) + (16.0 / 116.0);
z = (z > 0.008856)
? Math.Pow(z, 1.0 / 3.0)
: (7.787 * z) + (16.0 / 116.0);
Vector3 lab = new Vector3();
lab.X = (116 * y) - 16;
lab.Y = 500 * (x - y);
lab.Z = 200 * (y - z);
return lab;
}
private Vector3 RgbToLab (Color rgb)
{
return XyzToLab(RgbToXyz(rgb));
}
static MapConverter ()
{
_defaultColorIndex = new Color[] {
Color.FromArgb(0, 0, 0, 0), // Unexplored
Color.FromArgb(0, 0, 0, 0),
Color.FromArgb(0, 0, 0, 0),
Color.FromArgb(0, 0, 0, 0),
Color.FromArgb(89, 125, 39), // Grass
Color.FromArgb(109, 153, 48),
Color.FromArgb(127, 178, 56),
Color.FromArgb(109, 153, 48),
Color.FromArgb(174, 164, 115), // Sand/Gravel
Color.FromArgb(213, 201, 140),
Color.FromArgb(247, 233, 163),
Color.FromArgb(213, 201, 140),
Color.FromArgb(117, 117, 117), // Other
Color.FromArgb(144, 144, 144),
Color.FromArgb(167, 167, 167),
Color.FromArgb(144, 144, 144),
Color.FromArgb(180, 0, 0), // Lava
Color.FromArgb(220, 0, 0),
Color.FromArgb(255, 0, 0),
Color.FromArgb(220, 0, 0),
Color.FromArgb(112, 112, 180), // Ice
Color.FromArgb(138, 138, 220),
Color.FromArgb(160, 160, 255),
Color.FromArgb(138, 138, 220),
Color.FromArgb(117, 117, 117), // Iron
Color.FromArgb(144, 144, 144),
Color.FromArgb(167, 167, 167),
Color.FromArgb(144, 144, 144),
Color.FromArgb(0, 87, 0), // Leaves/Flowers
Color.FromArgb(0, 106, 0),
Color.FromArgb(0, 124, 0),
Color.FromArgb(0, 106, 0),
Color.FromArgb(180, 180, 180), // Snow
Color.FromArgb(220, 220, 220),
Color.FromArgb(255, 255, 255),
Color.FromArgb(220, 220, 220),
Color.FromArgb(115, 118, 129), // Clay
Color.FromArgb(141, 144, 158),
Color.FromArgb(164, 168, 184),
Color.FromArgb(141, 144, 158),
Color.FromArgb(129, 74, 33), // Dirt
Color.FromArgb(157, 91, 40),
Color.FromArgb(183, 106, 47),
Color.FromArgb(157, 91, 40),
Color.FromArgb(79, 79, 79), // Stone/Cobblestone/Ore
Color.FromArgb(96, 96, 96),
Color.FromArgb(112, 112, 112),
Color.FromArgb(96, 96, 96),
Color.FromArgb(45, 45, 180), // Water
Color.FromArgb(55, 55, 220),
Color.FromArgb(64, 64, 255),
Color.FromArgb(55, 55, 220),
Color.FromArgb(73, 58, 35), // Log/Tree/Wood
Color.FromArgb(89, 71, 43),
Color.FromArgb(104, 83, 50),
Color.FromArgb(89, 71, 43),
};
}
}
}

View file

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Ionic.Zlib;
using Substrate.Core;
namespace Substrate.Data
{
public class MapFile : NBTFile
{
public MapFile (string path)
: base(path)
{
}
public MapFile (string path, int id)
: base("")
{
if (!Directory.Exists(path)) {
Directory.CreateDirectory(path);
}
string file = "map_" + id + ".dat";
FileName = Path.Combine(path, file);
}
public static int IdFromFilename (string filename)
{
if (filename.EndsWith(".dat")) {
return Convert.ToInt32(filename.Substring(4).Remove(filename.Length - 4));
}
throw new FormatException("Filename '" + filename + "' is not a .dat file");
}
}
}

View file

@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Substrate.Nbt;
using System.Text.RegularExpressions;
namespace Substrate.Data
{
/// <summary>
/// Functions to manage all <see cref="Map"/> data resources.
/// </summary>
/// <remarks>This manager is intended for map files stored in standard compressed NBT format.</remarks>
public class MapManager : IMapManager, IEnumerable<Map>
{
private NbtWorld _world;
/// <summary>
/// Create a new <see cref="MapManager"/> for a given world.
/// </summary>
/// <param name="world">World containing data files.</param>
public MapManager (NbtWorld world)
{
_world = world;
}
/// <summary>
/// Gets a <see cref="MapFile"/> representing the backing NBT data stream.
/// </summary>
/// <param name="id">The id of the map to fetch.</param>
/// <returns>A <see cref="MapFile"/> for the given map.</returns>
protected MapFile GetMapFile (int id)
{
return new MapFile(Path.Combine(_world.Path, _world.DataDirectory), id);
}
/// <summary>
/// Gets a raw <see cref="NbtTree"/> of data for the given map.
/// </summary>
/// <param name="id">The id of the map to fetch.</param>
/// <returns>An <see cref="NbtTree"/> containing the given map's raw data.</returns>
/// <exception cref="NbtIOException">Thrown when the manager cannot read in an NBT data stream.</exception>
public NbtTree GetMapTree (int id)
{
MapFile mf = GetMapFile(id);
Stream nbtstr = mf.GetDataInputStream();
if (nbtstr == null) {
throw new NbtIOException("Failed to initialize NBT data stream for input.");
}
return new NbtTree(nbtstr);
}
/// <summary>
/// Saves a raw <see cref="NbtTree"/> representing a map to the given map's file.
/// </summary>
/// <param name="id">The id of the map to write data to.</param>
/// <param name="tree">The map's data as an <see cref="NbtTree"/>.</param>
/// <exception cref="NbtIOException">Thrown when the manager cannot initialize an NBT data stream for output.</exception>
public void SetMapTree (int id, NbtTree tree)
{
MapFile mf = GetMapFile(id);
Stream zipstr = mf.GetDataOutputStream();
if (zipstr == null) {
throw new NbtIOException("Failed to initialize NBT data stream for output.");
}
tree.WriteTo(zipstr);
zipstr.Close();
}
#region IMapManager Members
/// <inherit />
/// <exception cref="DataIOException">Thrown when the manager cannot read in a map that should exist.</exception>
public Map GetMap (int id)
{
if (!MapExists(id)) {
return null;
}
try {
Map m = new Map().LoadTreeSafe(GetMapTree(id).Root);
m.Id = id;
return m;
}
catch (Exception ex) {
DataIOException pex = new DataIOException("Could not load map", ex);
pex.Data["MapId"] = id;
throw pex;
}
}
/// <inherit />
/// <exception cref="DataIOException">Thrown when the manager cannot write out the map</exception>
public void SetMap (int id, Map map)
{
try {
SetMapTree(id, new NbtTree(map.BuildTree() as TagNodeCompound));
}
catch (Exception ex) {
DataIOException pex = new DataIOException("Could not save map", ex);
pex.Data["MapId"] = id;
throw pex;
}
}
/// <summary>
/// Saves a <see cref="Map"/> object's data back to file given the id set in the <see cref="Map"/> object.
/// </summary>
/// <param name="map">The <see cref="Map"/> object containing the data to write back.</param>
/// <exception cref="DataIOException">Thrown when the manager cannot write out the map</exception>
public void SetMap (Map map)
{
SetMap(map.Id, map);
}
/// <inherit />
public bool MapExists (int id)
{
return new MapFile(Path.Combine(_world.Path, _world.DataDirectory), id).Exists();
}
/// <inherit />
/// <exception cref="DataIOException">Thrown when the manager cannot delete the map.</exception>
public void DeleteMap (int id)
{
try {
new MapFile(Path.Combine(_world.Path, _world.DataDirectory), id).Delete();
}
catch (Exception ex) {
DataIOException pex = new DataIOException("Could not remove map", ex);
pex.Data["MapId"] = id;
throw pex;
}
}
#endregion
#region IEnumerable<Map> Members
/// <summary>
/// Gets an enumerator that iterates through all the maps in the world's data directory.
/// </summary>
/// <returns>An enumerator for this manager.</returns>
public IEnumerator<Map> GetEnumerator ()
{
string path = Path.Combine(_world.Path, _world.DataDirectory);
if (!Directory.Exists(path)) {
throw new DirectoryNotFoundException();
}
string[] files = Directory.GetFiles(path);
foreach (string file in files) {
string basename = Path.GetFileName(file);
if (!ParseFileName(basename)) {
continue;
}
int id = MapFile.IdFromFilename(basename);
yield return GetMap(id);
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
{
return GetEnumerator();
}
#endregion
private bool ParseFileName (string filename)
{
Match match = _namePattern.Match(filename);
if (!match.Success) {
return false;
}
return true;
}
private static Regex _namePattern = new Regex("^map_[0-9]+\\.dat$");
}
}

View file

@ -0,0 +1,37 @@
using System;
namespace Substrate.Data
{
/// <summary>
/// An interface of basic manipulations on an abstract data store for map data.
/// </summary>
public interface IMapManager
{
/// <summary>
/// Gets a <see cref="Map"/> object for the given map id from the underlying data store.
/// </summary>
/// <param name="id">The id of a map data resource.</param>
/// <returns>A <see cref="Map"/> object for the given map id, or <c>null</c> if the map doesn't exist.</returns>
Map GetMap (int id);
/// <summary>
/// Saves a <see cref="Map"/> object's data back to the underlying data store for the given map id.
/// </summary>
/// <param name="id">The id of the map to write back data for.</param>
/// <param name="map">The <see cref="Map"/> object containing data to write back.</param>
void SetMap (int id, Map map);
/// <summary>
/// Checks if a map exists in the underlying data store.
/// </summary>
/// <param name="id">The id of the map to look up.</param>
/// <returns>True if map data was found; false otherwise.</returns>
bool MapExists (int id);
/// <summary>
/// Deletes a map with the given id from the underlying data store.
/// </summary>
/// <param name="id">The id of the map to delete.</param>
void DeleteMap (int id);
}
}

View file

@ -6,7 +6,7 @@ namespace Substrate.Nbt
/// <summary>
/// The exception that is thrown when errors occur during Nbt IO operations.
/// </summary>
/// <remarks>In most cases, the <see cref="NbtIOException.InnerException"/> property will contain more detailed information on the
/// <remarks>In most cases, the <see cref="Exception.InnerException"/> property will contain more detailed information on the
/// error that occurred.</remarks>
[Serializable]
public class NbtIOException : SubstrateException

View file

@ -4,6 +4,7 @@ using System.ComponentModel;
using System.IO;
using Substrate.Core;
using Substrate.Nbt;
using Substrate.Data;
namespace Substrate
{
@ -18,12 +19,17 @@ namespace Substrate
/// open worlds of the new format.</para></remarks>
public abstract class NbtWorld
{
private const string _DATA_DIR = "data";
private string _path;
private string _dataDir;
/// <summary>
/// Creates a new instance of an <see cref="NbtWorld"/> object.
/// </summary>
protected NbtWorld () { }
protected NbtWorld () {
_dataDir = _DATA_DIR;
}
/// <summary>
/// Gets or sets the path to the directory containing the world.
@ -34,6 +40,15 @@ namespace Substrate
set { _path = value; }
}
/// <summary>
/// Gets or sets the directory containing data resources, rooted in the world directory.
/// </summary>
public string DataDirectory
{
get { return _dataDir; }
set { _dataDir = value; }
}
/// <summary>
/// Gets a reference to this world's <see cref="Level"/> object.
/// </summary>
@ -86,6 +101,15 @@ namespace Substrate
return GetPlayerManagerVirt();
}
/// <summary>
/// Gets a <see cref="DataManager"/> for managing data resources, such as maps.
/// </summary>
/// <returns>A <see cref="DataManager"/> for this world.</returns>
public DataManager GetDataManager ()
{
return GetDataManagerVirt();
}
/// <summary>
/// Attempts to determine the best matching world type of the given path, and open the world as that type.
/// </summary>
@ -139,6 +163,15 @@ namespace Substrate
/// <returns>An <see cref="IPlayerManager"/> for the given dimension in the world.</returns>
protected abstract IPlayerManager GetPlayerManagerVirt ();
/// <summary>
/// Virtual implementor of <see cref="GetDataManager"/>
/// </summary>
/// <returns>A <see cref="DataManager"/> for the given dimension in the world.</returns>
protected virtual DataManager GetDataManagerVirt ()
{
throw new NotImplementedException();
}
#endregion
static NbtWorld ()

View file

@ -144,7 +144,7 @@ namespace Substrate
new PlayerFile(_playerPath, name).Delete();
}
catch (Exception ex) {
PlayerIOException pex = new PlayerIOException("Could not save player", ex);
PlayerIOException pex = new PlayerIOException("Could not remove player", ex);
pex.Data["PlayerName"] = name;
throw pex;
}
@ -153,7 +153,7 @@ namespace Substrate
#region IEnumerable<Player> Members
/// <summary>
/// Gets an enumerator that iterates through all the chunks in the world.
/// Gets an enumerator that iterates through all the players in the world.
/// </summary>
/// <returns>An enumerator for this manager.</returns>
public IEnumerator<Player> GetEnumerator ()

View file

@ -60,6 +60,7 @@
<HintPath>Assemblies\Ionic.Zlib.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Drawing" />
</ItemGroup>
<ItemGroup>
<Compile Include="Source\AlphaWorld.cs" />
@ -67,9 +68,17 @@
<Compile Include="Source\BetaWorld.cs" />
<Compile Include="Source\Core\BoundedBlockInterface.cs" />
<Compile Include="Source\Core\ItemInterface.cs" />
<Compile Include="Source\Data\BetaDataManager.cs" />
<Compile Include="Source\Data\DataExceptions.cs" />
<Compile Include="Source\Data\MapFile.cs" />
<Compile Include="Source\Core\OpenWorldEvent.cs" />
<Compile Include="Source\Core\RegionInterface.cs" />
<Compile Include="Source\Core\UnboundedBlockInterface.cs" />
<Compile Include="Source\Data\DataManager.cs" />
<Compile Include="Source\Data\Map.cs" />
<Compile Include="Source\Data\MapConverter.cs" />
<Compile Include="Source\Data\MapManager.cs" />
<Compile Include="Source\Data\MapManagerInterface.cs" />
<Compile Include="Source\Entities\EntityBlaze.cs" />
<Compile Include="Source\Entities\EntityCaveSpider.cs" />
<Compile Include="Source\Entities\EntityEnderDragon.cs" />