forked from mirrors/NBTExplorer
Data resource management (maps)
This commit is contained in:
parent
d9230b0d31
commit
3b781d0a02
14 changed files with 1398 additions and 9 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
121
SubstrateCS/Source/Data/BetaDataManager.cs
Normal file
121
SubstrateCS/Source/Data/BetaDataManager.cs
Normal 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
|
||||
}
|
||||
}
|
46
SubstrateCS/Source/Data/DataExceptions.cs
Normal file
46
SubstrateCS/Source/Data/DataExceptions.cs
Normal 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)
|
||||
{ }
|
||||
}
|
||||
}
|
48
SubstrateCS/Source/Data/DataManager.cs
Normal file
48
SubstrateCS/Source/Data/DataManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
342
SubstrateCS/Source/Data/Map.cs
Normal file
342
SubstrateCS/Source/Data/Map.cs
Normal 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
|
||||
}
|
||||
}
|
464
SubstrateCS/Source/Data/MapConverter.cs
Normal file
464
SubstrateCS/Source/Data/MapConverter.cs
Normal 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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
37
SubstrateCS/Source/Data/MapFile.cs
Normal file
37
SubstrateCS/Source/Data/MapFile.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
190
SubstrateCS/Source/Data/MapManager.cs
Normal file
190
SubstrateCS/Source/Data/MapManager.cs
Normal 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$");
|
||||
}
|
||||
}
|
37
SubstrateCS/Source/Data/MapManagerInterface.cs
Normal file
37
SubstrateCS/Source/Data/MapManagerInterface.cs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 ()
|
||||
|
|
|
@ -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 ()
|
||||
|
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in a new issue