using System; using System.Collections.Generic; using System.IO; using Substrate.Core; using Substrate.Nbt; using Substrate.Data; //TODO: Exceptions (+ Alpha) namespace Substrate { using IO = System.IO; /// /// Represents a Beta-compatible (Beta 1.3 or higher) Minecraft world. /// public class BetaWorld : NbtWorld { private const string _REGION_DIR = "region"; private const string _PLAYER_DIR = "players"; private string _levelFile = "level.dat"; private Level _level; private Dictionary _regionMgrs; private Dictionary _chunkMgrs; private Dictionary _blockMgrs; private Dictionary _caches; private PlayerManager _playerMan; private BetaDataManager _dataMan; private int _prefCacheSize = 256; private BetaWorld () { _regionMgrs = new Dictionary(); _chunkMgrs = new Dictionary(); _blockMgrs = new Dictionary(); _caches = new Dictionary(); } /// /// Gets a reference to this world's object. /// public override Level Level { get { return _level; } } /// /// Gets a for the default dimension. /// /// A tied to the default dimension in this world. /// Get a if you need to manage blocks as a global, unbounded matrix. This abstracts away /// any higher-level organizational divisions. If your task is going to be heavily performance-bound, consider getting a /// instead and working with blocks on a chunk-local level. public new BlockManager GetBlockManager () { return GetBlockManagerVirt(Dimension.DEFAULT) as BlockManager; } /// /// Gets a for the given dimension. /// /// The id of the dimension to look up. /// A tied to the given dimension in this world. /// Get a if you need to manage blocks as a global, unbounded matrix. This abstracts away /// any higher-level organizational divisions. If your task is going to be heavily performance-bound, consider getting a /// instead and working with blocks on a chunk-local level. public new BlockManager GetBlockManager (int dim) { return GetBlockManagerVirt(dim) as BlockManager; } public new BlockManager GetBlockManager (string dim) { return GetBlockManagerVirt(dim) as BlockManager; } /// /// Gets a for the default dimension. /// /// A tied to the default dimension in this world. /// Get a if you you need to work with easily-digestible, bounded chunks of blocks. public new RegionChunkManager GetChunkManager () { return GetChunkManagerVirt(Dimension.DEFAULT) as RegionChunkManager; } /// /// Gets a for the given dimension. /// /// The id of the dimension to look up. /// A tied to the given dimension in this world. /// Get a if you you need to work with easily-digestible, bounded chunks of blocks. public new RegionChunkManager GetChunkManager (int dim) { return GetChunkManagerVirt(dim) as RegionChunkManager; } public new RegionChunkManager GetChunkManager (string dim) { return GetChunkManagerVirt(dim) as RegionChunkManager; } /// /// Gets a for the default dimension. /// /// A tied to the defaul dimension in this world. /// Regions are a higher-level unit of organization for blocks unique to worlds created in Beta 1.3 and beyond. /// Consider using the if you are interested in working with blocks. public BetaRegionManager GetRegionManager () { return GetRegionManager(Dimension.DEFAULT); } /// /// Gets a for the given dimension. /// /// The id of the dimension to look up. /// A tied to the given dimension in this world. /// Regions are a higher-level unit of organization for blocks unique to worlds created in Beta 1.3 and beyond. /// Consider using the if you are interested in working with blocks. public BetaRegionManager GetRegionManager (int dim) { return GetRegionManager(DimensionFromInt(dim)); } public BetaRegionManager GetRegionManager (string dim) { BetaRegionManager rm; if (_regionMgrs.TryGetValue(dim, out rm)) { return rm; } OpenDimension(dim); return _regionMgrs[dim]; } /// /// Gets a for maanging players on multiplayer worlds. /// /// A for this world. /// To manage the player of a single-player world, get a object for the world instead. public new PlayerManager GetPlayerManager () { return GetPlayerManagerVirt() as PlayerManager; } /// /// Gets a for managing data resources, such as maps. /// /// A for this world. public new BetaDataManager GetDataManager () { return GetDataManagerVirt() as BetaDataManager; } /// public override void Save () { _level.Save(); foreach (KeyValuePair cm in _chunkMgrs) { cm.Value.Save(); } } /// /// Gets the currently managing chunks in the default dimension. /// /// The for the default dimension, or null if the dimension was not found. public ChunkCache GetChunkCache () { return GetChunkCache(Dimension.DEFAULT); } /// /// Gets the currently managing chunks in the given dimension. /// /// The id of a dimension to look up. /// The for the given dimension, or null if the dimension was not found. public ChunkCache GetChunkCache (int dim) { return GetChunkCache(DimensionFromInt(dim)); } public ChunkCache GetChunkCache (string dim) { if (_caches.ContainsKey(dim)) { return _caches[dim]; } return null; } /// /// Opens an existing Beta-compatible Minecraft world and returns a new to represent it. /// /// The path to the directory containing the world's level.dat, or the path to level.dat itself. /// A new object representing an existing world on disk. public static new BetaWorld Open (string path) { return new BetaWorld().OpenWorld(path) as BetaWorld; } /// /// Opens an existing Beta-compatible Minecraft world and returns a new to represent it. /// /// The path to the directory containing the world's level.dat, or the path to level.dat itself. /// The preferred cache size in chunks for each opened dimension in this world. /// A new object representing an existing world on disk. public static BetaWorld Open (string path, int cacheSize) { BetaWorld world = new BetaWorld().OpenWorld(path); world._prefCacheSize = cacheSize; return world; } /// /// Creates a new Beta-compatible Minecraft world and returns a new to represent it. /// /// The path to the directory where the new world should be stored. /// A new object representing a new world. /// This method will attempt to create the specified directory immediately if it does not exist, but will not /// write out any world data unless it is explicitly saved at a later time. public static BetaWorld Create (string path) { return new BetaWorld().CreateWorld(path) as BetaWorld; } /// /// Creates a new Beta-compatible Minecraft world and returns a new to represent it. /// /// The path to the directory where the new world should be stored. /// The preferred cache size in chunks for each opened dimension in this world. /// A new object representing a new world. /// This method will attempt to create the specified directory immediately if it does not exist, but will not /// write out any world data unless it is explicitly saved at a later time. public static BetaWorld Create (string path, int cacheSize) { BetaWorld world = new BetaWorld().CreateWorld(path); world._prefCacheSize = cacheSize; return world; } /// protected override IBlockManager GetBlockManagerVirt (int dim) { return GetBlockManagerVirt(DimensionFromInt(dim)); } protected override IBlockManager GetBlockManagerVirt (string dim) { BlockManager rm; if (_blockMgrs.TryGetValue(dim, out rm)) { return rm; } OpenDimension(dim); return _blockMgrs[dim]; } /// protected override IChunkManager GetChunkManagerVirt (int dim) { return GetChunkManagerVirt(DimensionFromInt(dim)); } protected override IChunkManager GetChunkManagerVirt (string dim) { RegionChunkManager rm; if (_chunkMgrs.TryGetValue(dim, out rm)) { return rm; } OpenDimension(dim); return _chunkMgrs[dim]; } /// protected override IPlayerManager GetPlayerManagerVirt () { if (_playerMan != null) { return _playerMan; } string path = IO.Path.Combine(Path, _PLAYER_DIR); _playerMan = new PlayerManager(path); return _playerMan; } /// protected override Data.DataManager GetDataManagerVirt () { if (_dataMan != null) { return _dataMan; } _dataMan = new BetaDataManager(this); return _dataMan; } private string DimensionFromInt (int dim) { if (dim == Dimension.DEFAULT) return ""; else return "DIM" + dim; } private void OpenDimension (string dim) { string path = Path; if (String.IsNullOrEmpty(dim)) { path = IO.Path.Combine(path, _REGION_DIR); } else { path = IO.Path.Combine(path, dim); path = IO.Path.Combine(path, _REGION_DIR); } if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } ChunkCache cc = new ChunkCache(_prefCacheSize); BetaRegionManager rm = new BetaRegionManager(path, cc); RegionChunkManager cm = new RegionChunkManager(rm, cc); BlockManager bm = new AlphaBlockManager(cm); _regionMgrs[dim] = rm; _chunkMgrs[dim] = cm; _blockMgrs[dim] = bm; _caches[dim] = cc; } private BetaWorld OpenWorld (string path) { if (!Directory.Exists(path)) { if (File.Exists(path)) { _levelFile = IO.Path.GetFileName(path); path = IO.Path.GetDirectoryName(path); } else { throw new DirectoryNotFoundException("Directory '" + path + "' not found"); } } Path = path; string ldat = IO.Path.Combine(path, _levelFile); if (!File.Exists(ldat)) { throw new FileNotFoundException("Data file '" + _levelFile + "' not found in '" + path + "'", ldat); } if (!LoadLevel()) { throw new Exception("Failed to load '" + _levelFile + "'"); } return this; } private BetaWorld CreateWorld (string path) { if (!Directory.Exists(path)) { throw new DirectoryNotFoundException("Directory '" + path + "' not found"); } string regpath = IO.Path.Combine(path, _REGION_DIR); if (!Directory.Exists(regpath)) { Directory.CreateDirectory(regpath); } Path = path; _level = new Level(this); _level.Version = 19132; return this; } private bool LoadLevel () { NBTFile nf = new NBTFile(IO.Path.Combine(Path, _levelFile)); Stream nbtstr = nf.GetDataInputStream(); if (nbtstr == null) { return false; } NbtTree tree = new NbtTree(nbtstr); _level = new Level(this); _level = _level.LoadTreeSafe(tree.Root); return _level != null; } internal static void OnResolveOpen (object sender, OpenWorldEventArgs e) { try { BetaWorld world = new BetaWorld().OpenWorld(e.Path); if (world == null) { return; } string regPath = IO.Path.Combine(e.Path, _REGION_DIR); if (!Directory.Exists(regPath)) { return; } if (world.Level.Version != 19132) { return; } e.AddHandler(Open); } catch (Exception) { return; } } } }