using System; using System.IO; using Substrate.Core; using Substrate.Nbt; namespace Substrate { /// /// Specifies the type of gameplay associated with a world. /// public enum GameType { /// /// The world will be played in Survival mode. /// SURVIVAL = 0, /// /// The world will be played in Creative mode. /// CREATIVE = 1, } public enum TimeOfDay { Daytime = 0, Noon = 6000, Sunset = 12000, Nighttime = 13800, Midnight = 18000, Sunrise = 22200, } /// /// Represents general data and metadata of a single world. /// public class Level : INbtObject, ICopyable { private static SchemaNodeCompound _schema = new SchemaNodeCompound() { new SchemaNodeCompound("Data") { new SchemaNodeScaler("Time", TagType.TAG_LONG), new SchemaNodeScaler("LastPlayed", TagType.TAG_LONG, SchemaOptions.CREATE_ON_MISSING), new SchemaNodeCompound("Player", Player.Schema, SchemaOptions.OPTIONAL), new SchemaNodeScaler("SpawnX", TagType.TAG_INT), new SchemaNodeScaler("SpawnY", TagType.TAG_INT), new SchemaNodeScaler("SpawnZ", TagType.TAG_INT), new SchemaNodeScaler("SizeOnDisk", TagType.TAG_LONG, SchemaOptions.CREATE_ON_MISSING), new SchemaNodeScaler("RandomSeed", TagType.TAG_LONG), new SchemaNodeScaler("version", TagType.TAG_INT, SchemaOptions.OPTIONAL), new SchemaNodeScaler("LevelName", TagType.TAG_STRING, SchemaOptions.OPTIONAL), new SchemaNodeScaler("raining", TagType.TAG_BYTE, SchemaOptions.OPTIONAL), new SchemaNodeScaler("thundering", TagType.TAG_BYTE, SchemaOptions.OPTIONAL), new SchemaNodeScaler("rainTime", TagType.TAG_INT, SchemaOptions.OPTIONAL), new SchemaNodeScaler("thunderTime", TagType.TAG_INT, SchemaOptions.OPTIONAL), new SchemaNodeScaler("GameType", TagType.TAG_INT, SchemaOptions.OPTIONAL), new SchemaNodeScaler("MapFeatures", TagType.TAG_BYTE, SchemaOptions.OPTIONAL), new SchemaNodeScaler("hardcore", TagType.TAG_BYTE, SchemaOptions.OPTIONAL), }, }; private TagNodeCompound _source; private NbtWorld _world; private long _time; private long _lastPlayed; private Player _player; private int _spawnX; private int _spawnY; private int _spawnZ; private long _sizeOnDisk; private long _randomSeed; private int? _version; private string _name; private byte? _raining; private byte? _thundering; private int? _rainTime; private int? _thunderTime; private int? _gameType; private byte? _mapFeatures; private byte? _hardcore; /// /// Gets or sets the creation time of the world as a long timestamp. /// public long Time { get { return _time; } set { _time = value; } } /// /// Gets or sets the time that the world was last played as a long timestamp. /// public long LastPlayed { get { return _lastPlayed; } set { _lastPlayed = value; } } /// /// Gets or sets the player for single-player worlds. /// public Player Player { get { return _player; } set { _player = value; _player.World = _name; } } /// /// Gets or sets the world's spawn point. /// public SpawnPoint Spawn { get { return new SpawnPoint(_spawnX, _spawnY, _spawnZ); } set { _spawnX = value.X; _spawnY = value.Y; _spawnZ = value.Z; } } /// /// Gets the estimated size of the world in bytes. /// public long SizeOnDisk { get { return _sizeOnDisk; } } /// /// Gets or sets the world's random seed. /// public long RandomSeed { get { return _randomSeed; } set { _randomSeed = value; } } /// /// Gets or sets the world's version number. /// public int Version { get { return _version ?? 0; } set { _version = value; } } /// /// Gets or sets the name of the world. /// /// If there is a object attached to this world, the player's world field /// will also be updated. public string LevelName { get { return _name; } set { _name = value; if (_player != null) { _player.World = value; } } } /// /// Gets or sets a value indicating that it is raining in the world. /// public bool IsRaining { get { return (_raining ?? 0) == 1; } set { _raining = value ? (byte)1 : (byte)0; } } /// /// Gets or sets a value indicating that it is thunderstorming in the world. /// public bool IsThundering { get { return (_thundering ?? 0) == 1; } set { _thundering = value ? (byte)1 : (byte)0; } } /// /// Gets or sets the timer value for controlling rain. /// public int RainTime { get { return _rainTime ?? 0; } set { _rainTime = value; } } /// /// Gets or sets the timer value for controlling thunderstorms. /// public int ThunderTime { get { return _thunderTime ?? 0; } set { _thunderTime = value; } } /// /// Gets or sets the game type associated with this world. /// public GameType GameType { get { return (GameType)(_gameType ?? 0); } set { _gameType = (int)value; } } /// /// Gets or sets a value indicating that structures (dungeons, villages, ...) will be generated. /// public bool UseMapFeatures { get { return (_mapFeatures ?? 0) == 1; } set { _mapFeatures = value ? (byte)1 : (byte)0; } } /// /// Gets or sets a value indicating whether the map is hardcore mode /// public bool Hardcore { get { return (_hardcore ?? 0) == 1; } set { _hardcore = value ? (byte)1 : (byte)0; } } /// /// Gets the source used to create this if it exists. /// public TagNodeCompound Source { get { return _source; } } /// /// Gets a representing the schema of a level. /// public static SchemaNodeCompound Schema { get { return _schema; } } /// /// Creates a new object with reasonable defaults tied to the given world. /// /// The world that the should be tied to. public Level (NbtWorld world) { _world = world; // Sane defaults _time = 0; _lastPlayed = 0; _spawnX = 0; _spawnY = 64; _spawnZ = 0; _sizeOnDisk = 0; _randomSeed = new Random().Next(); //_version = 19132; _version = 19133; _name = "Untitled"; _hardcore = 0; GameType = GameType.SURVIVAL; UseMapFeatures = true; _source = new TagNodeCompound(); } /// /// Creates a copy of an existing object. /// /// The object to copy. protected Level (Level p) { _world = p._world; _time = p._time; _lastPlayed = p._lastPlayed; _spawnX = p._spawnX; _spawnY = p._spawnY; _spawnZ = p._spawnZ; _sizeOnDisk = p._sizeOnDisk; _randomSeed = p._randomSeed; _version = p._version; _name = p._name; _raining = p._raining; _thundering = p._thundering; _rainTime = p._rainTime; _thunderTime = p._thunderTime; _gameType = p._gameType; _mapFeatures = p._mapFeatures; _hardcore = p._hardcore; if (p._player != null) { _player = p._player.Copy(); } if (p._source != null) { _source = p._source.Copy() as TagNodeCompound; } } /// /// Creates a default player entry for this world. /// public void SetDefaultPlayer () { _player = new Player(); _player.World = _name; _player.Position.X = _spawnX; _player.Position.Y = _spawnY + 1.7; _player.Position.Z = _spawnZ; } /// /// Saves a object to disk as a standard compressed NBT stream. /// /// True if the level was saved; false otherwise. /// Thrown when an error is encountered writing out the level. public bool Save () { if (_world == null) { return false; } try { NBTFile nf = new NBTFile(Path.Combine(_world.Path, "level.dat")); Stream zipstr = nf.GetDataOutputStream(); if (zipstr == null) { NbtIOException nex = new NbtIOException("Failed to initialize compressed NBT stream for output"); nex.Data["Level"] = this; throw nex; } new NbtTree(BuildTree() as TagNodeCompound).WriteTo(zipstr); zipstr.Close(); return true; } catch (Exception ex) { LevelIOException lex = new LevelIOException("Could not save level file.", ex); lex.Data["Level"] = this; throw lex; } } #region INBTObject Members /// /// Attempt to load a Level subtree into the without validation. /// /// The root node of a Level subtree. /// The returns itself on success, or null if the tree was unparsable. public virtual Level LoadTree (TagNode tree) { TagNodeCompound dtree = tree as TagNodeCompound; if (dtree == null) { return null; } _version = null; _raining = null; _rainTime = null; _thundering = null; _thunderTime = null; _gameType = null; _mapFeatures = null; TagNodeCompound ctree = dtree["Data"].ToTagCompound(); _time = ctree["Time"].ToTagLong(); _lastPlayed = ctree["LastPlayed"].ToTagLong(); if (ctree.ContainsKey("Player")) { _player = new Player().LoadTree(ctree["Player"]); } _spawnX = ctree["SpawnX"].ToTagInt(); _spawnY = ctree["SpawnY"].ToTagInt(); _spawnZ = ctree["SpawnZ"].ToTagInt(); _sizeOnDisk = ctree["SizeOnDisk"].ToTagLong(); _randomSeed = ctree["RandomSeed"].ToTagLong(); if (ctree.ContainsKey("version")) { _version = ctree["version"].ToTagInt(); } if (ctree.ContainsKey("LevelName")) { _name = ctree["LevelName"].ToTagString(); } if (ctree.ContainsKey("raining")) { _raining = ctree["raining"].ToTagByte(); } if (ctree.ContainsKey("thundering")) { _thundering = ctree["thundering"].ToTagByte(); } if (ctree.ContainsKey("rainTime")) { _rainTime = ctree["rainTime"].ToTagInt(); } if (ctree.ContainsKey("thunderTime")) { _thunderTime = ctree["thunderTime"].ToTagInt(); } if (ctree.ContainsKey("GameType")) { _gameType = ctree["GameType"].ToTagInt(); } if (ctree.ContainsKey("MapFeatures")) { _mapFeatures = ctree["MapFeatures"].ToTagByte(); } if (ctree.ContainsKey("hardcore")) { _hardcore = ctree["hardcore"].ToTagByte(); } _source = ctree.Copy() as TagNodeCompound; return this; } /// /// Attempt to load a Level subtree into the with validation. /// /// The root node of a Level subtree. /// The returns itself on success, or null if the tree failed validation. public virtual Level LoadTreeSafe (TagNode tree) { if (!ValidateTree(tree)) { return null; } return LoadTree(tree); } /// /// Builds a Level subtree from the current data. /// /// The root node of a Level subtree representing the current data. public virtual TagNode BuildTree () { TagNodeCompound data = new TagNodeCompound(); data["Time"] = new TagNodeLong(_time); data["LastPlayed"] = new TagNodeLong(_lastPlayed); if (_player != null) { data["Player"] = _player.BuildTree(); } data["SpawnX"] = new TagNodeInt(_spawnX); data["SpawnY"] = new TagNodeInt(_spawnY); data["SpawnZ"] = new TagNodeInt(_spawnZ); data["SizeOnDisk"] = new TagNodeLong(_sizeOnDisk); data["RandomSeed"] = new TagNodeLong(_randomSeed); if (_version != null && _version != 0) { data["version"] = new TagNodeInt(_version ?? 0); } if (_name != null) { data["LevelName"] = new TagNodeString(_name); } if (_raining != null) { data["raining"] = new TagNodeByte(_raining ?? 0); } if (_thundering != null) { data["thundering"] = new TagNodeByte(_thundering ?? 0); } if (_rainTime != null) { data["rainTime"] = new TagNodeInt(_rainTime ?? 0); } if (_thunderTime != null) { data["thunderTime"] = new TagNodeInt(_thunderTime ?? 0); } if (_gameType != null) { data["GameType"] = new TagNodeInt(_gameType ?? 0); } if (_mapFeatures != null) { data["MapFeatures"] = new TagNodeByte(_mapFeatures ?? 0); } if (_hardcore != null) { data["hardcore"] = new TagNodeByte(_hardcore ?? 0); } if (_source != null) { data.MergeFrom(_source); } TagNodeCompound tree = new TagNodeCompound(); tree.Add("Data", data); return tree; } /// /// Validate a Level subtree against a schema defintion. /// /// The root node of a Level subtree. /// Status indicating whether the tree was valid against the internal schema. public virtual bool ValidateTree (TagNode tree) { return new NbtVerifier(tree, _schema).Verify(); } #endregion #region ICopyable Members /// /// Creates a deep-copy of the . /// /// A deep-copy of the , including a copy of the , if one is attached. public virtual Level Copy () { return new Level(this); } #endregion } }