From 99e986b338acaf0c3611ff6410f483a60cae2b7a Mon Sep 17 00:00:00 2001 From: Justin Aquadro Date: Wed, 6 Apr 2011 04:43:54 +0000 Subject: [PATCH] Forking Substrate to separate source tree --- Substrate/SubstrateCS/Source/Block.cs | 132 ++++ Substrate/SubstrateCS/Source/BlockInfo.cs | 499 ++++++++++++++ .../SubstrateCS/Source/BlockInterface.cs | 62 ++ Substrate/SubstrateCS/Source/BlockKey.cs | 54 ++ Substrate/SubstrateCS/Source/BlockManager.cs | 251 +++++++ Substrate/SubstrateCS/Source/BlockRef.cs | 206 ++++++ Substrate/SubstrateCS/Source/Chunk.cs | 524 +++++++++++++++ .../SubstrateCS/Source/ChunkEnumerator.cs | 156 +++++ Substrate/SubstrateCS/Source/ChunkFile.cs | 87 +++ .../SubstrateCS/Source/ChunkFileManager.cs | 188 ++++++ .../SubstrateCS/Source/ChunkInterface.cs | 48 ++ Substrate/SubstrateCS/Source/ChunkKey.cs | 51 ++ Substrate/SubstrateCS/Source/ChunkManager.cs | 196 ++++++ Substrate/SubstrateCS/Source/ChunkRef.cs | 328 +++++++++ Substrate/SubstrateCS/Source/Entity.cs | 134 ++++ Substrate/SubstrateCS/Source/Item.cs | 227 +++++++ .../SubstrateCS/Source/NBT/JSONSerializer.cs | 167 +++++ Substrate/SubstrateCS/Source/NBT/NBT.cs | 510 ++++++++++++++ Substrate/SubstrateCS/Source/NBT/NBTSchema.cs | 295 ++++++++ Substrate/SubstrateCS/Source/NBT/NBTValues.cs | 630 +++++++++++++++++ .../SubstrateCS/Source/NBT/NBTVerifier.cs | 241 +++++++ Substrate/SubstrateCS/Source/Region.cs | 334 +++++++++ .../SubstrateCS/Source/RegionEnumerator.cs | 131 ++++ Substrate/SubstrateCS/Source/RegionFile.cs | 442 ++++++++++++ Substrate/SubstrateCS/Source/RegionKey.cs | 51 ++ Substrate/SubstrateCS/Source/RegionManager.cs | 76 +++ Substrate/SubstrateCS/Source/TileEntity.cs | 631 ++++++++++++++++++ .../SubstrateCS/Source/TileEntityFactory.cs | 64 ++ .../SubstrateCS/Source/Utility/Interface.cs | 11 + .../SubstrateCS/Source/Utility/NibbleArray.cs | 62 ++ Substrate/SubstrateCS/Source/World.cs | 40 ++ 31 files changed, 6828 insertions(+) create mode 100644 Substrate/SubstrateCS/Source/Block.cs create mode 100644 Substrate/SubstrateCS/Source/BlockInfo.cs create mode 100644 Substrate/SubstrateCS/Source/BlockInterface.cs create mode 100644 Substrate/SubstrateCS/Source/BlockKey.cs create mode 100644 Substrate/SubstrateCS/Source/BlockManager.cs create mode 100644 Substrate/SubstrateCS/Source/BlockRef.cs create mode 100644 Substrate/SubstrateCS/Source/Chunk.cs create mode 100644 Substrate/SubstrateCS/Source/ChunkEnumerator.cs create mode 100644 Substrate/SubstrateCS/Source/ChunkFile.cs create mode 100644 Substrate/SubstrateCS/Source/ChunkFileManager.cs create mode 100644 Substrate/SubstrateCS/Source/ChunkInterface.cs create mode 100644 Substrate/SubstrateCS/Source/ChunkKey.cs create mode 100644 Substrate/SubstrateCS/Source/ChunkManager.cs create mode 100644 Substrate/SubstrateCS/Source/ChunkRef.cs create mode 100644 Substrate/SubstrateCS/Source/Entity.cs create mode 100644 Substrate/SubstrateCS/Source/Item.cs create mode 100644 Substrate/SubstrateCS/Source/NBT/JSONSerializer.cs create mode 100644 Substrate/SubstrateCS/Source/NBT/NBT.cs create mode 100644 Substrate/SubstrateCS/Source/NBT/NBTSchema.cs create mode 100644 Substrate/SubstrateCS/Source/NBT/NBTValues.cs create mode 100644 Substrate/SubstrateCS/Source/NBT/NBTVerifier.cs create mode 100644 Substrate/SubstrateCS/Source/Region.cs create mode 100644 Substrate/SubstrateCS/Source/RegionEnumerator.cs create mode 100644 Substrate/SubstrateCS/Source/RegionFile.cs create mode 100644 Substrate/SubstrateCS/Source/RegionKey.cs create mode 100644 Substrate/SubstrateCS/Source/RegionManager.cs create mode 100644 Substrate/SubstrateCS/Source/TileEntity.cs create mode 100644 Substrate/SubstrateCS/Source/TileEntityFactory.cs create mode 100644 Substrate/SubstrateCS/Source/Utility/Interface.cs create mode 100644 Substrate/SubstrateCS/Source/Utility/NibbleArray.cs create mode 100644 Substrate/SubstrateCS/Source/World.cs diff --git a/Substrate/SubstrateCS/Source/Block.cs b/Substrate/SubstrateCS/Source/Block.cs new file mode 100644 index 0000000..bc646a4 --- /dev/null +++ b/Substrate/SubstrateCS/Source/Block.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + using NBT; + using Utility; + + public class Block : IBlock, ICopyable + { + private int _id; + private int _data; + private int _skylight; + private int _blocklight; + + private TileEntity _tileEntity; + + public BlockInfo Info + { + get { return BlockInfo.BlockTable[_id]; } + } + + public int ID + { + get { return _id; } + set + { + BlockInfoEx info1 = BlockInfo.BlockTable[_id] as BlockInfoEx; + BlockInfoEx info2 = BlockInfo.BlockTable[value] as BlockInfoEx; + + if (info1 != info2) { + if (info1 != null) { + _tileEntity = null; + } + + if (info2 != null) { + _tileEntity = TileEntityFactory.Create(info2.TileEntityName); + } + } + + _id = value; + } + } + + public int Data + { + get { return _data; } + set + { + if (BlockManager.EnforceDataLimits && BlockInfo.BlockTable[_id] != null) { + if (!BlockInfo.BlockTable[_id].TestData(value)) { + return; + } + } + _data = value; + } + } + + public int SkyLight + { + get { return _skylight; } + set { _skylight = value; } + } + + public int BlockLight + { + get { return _blocklight; } + set { _blocklight = value; } + } + + public Block (int id) + { + _id = id; + } + + public Block (int id, int data) + { + _id = id; + _data = data; + } + + public Block (IChunk chunk, int lx, int ly, int lz) + { + _id = chunk.GetBlockID(lx, ly, lz); + _data = chunk.GetBlockData(lx, ly, lz); + _skylight = chunk.GetBlockSkyLight(lx, ly, lz); + _blocklight = chunk.GetBlockLight(lx, ly, lz); + _tileEntity = chunk.GetTileEntity(lx, ly, lz).Copy(); + } + + public TileEntity GetTileEntity () + { + return _tileEntity; + } + + public bool SetTileEntity (TileEntity te) + { + BlockInfoEx info = BlockInfo.BlockTable[_id] as BlockInfoEx; + if (info == null) { + return false; + } + + if (te.GetType() != TileEntityFactory.Lookup(info.TileEntityName)) { + return false; + } + + _tileEntity = te; + return true; + } + + public bool ClearTileEntity () + { + _tileEntity = null; + return true; + } + + #region ICopyable Members + + public Block Copy () + { + Block block = new Block(_id, _data); + block._blocklight = _blocklight; + block._skylight = _skylight; + block._tileEntity = _tileEntity.Copy(); + + return block; + } + + #endregion + } +} diff --git a/Substrate/SubstrateCS/Source/BlockInfo.cs b/Substrate/SubstrateCS/Source/BlockInfo.cs new file mode 100644 index 0000000..266537e --- /dev/null +++ b/Substrate/SubstrateCS/Source/BlockInfo.cs @@ -0,0 +1,499 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + using NBT; + + public enum BlockType + { + AIR = 0, + STONE = 1, + GRASS = 2, + DIRT = 3, + COBBLESTONE = 4, + WOOD_PLANK = 5, + SAPLING = 6, + BEDROCK = 7, + WATER = 8, + STATIONARY_WATER = 9, + LAVA = 10, + STATIONARY_LAVA = 11, + SAND = 12, + GRAVEL = 13, + GOLD_ORE = 14, + IRON_ORE = 15, + COAL_ORE = 16, + WOOD = 17, + LEAVES = 18, + SPONGE = 19, + GLASS = 20, + LAPIS_ORE = 21, + LAPIS_BLOCK = 22, + DISPENSER = 23, + SANDSTONE = 24, + NOTE_BLOCK = 25, + BED = 26, + WOOL = 35, + YELLOW_FLOWER = 37, + RED_ROSE = 38, + BROWN_MUSHROOM = 39, + RED_MUSHROOM = 40, + GOLD_BLOCK = 41, + IRON_BLOCK = 42, + DOUBLE_SLAB = 43, + SLAB = 44, + BRICK_BLOCK = 45, + TNT = 46, + BOOKSHELF = 47, + MOSS_STONE = 48, + OBSIDIAN = 49, + TORCH = 50, + FIRE = 51, + MONSTER_SPAWNER = 52, + WOOD_STAIRS = 53, + CHEST = 54, + REDSTONE_WIRE = 55, + DIAMOND_ORE = 56, + DIAMOND_BLOCK = 57, + CRAFTING_TABLE = 58, + CROPS = 59, + FARMLAND = 60, + FURNACE = 61, + BURNING_FURNACE = 62, + SIGN_POST = 63, + WOOD_DOOR = 64, + LADDER = 65, + RAILS = 66, + COBBLESTONE_STAIRS = 67, + WALL_SIGN = 68, + LEVER = 69, + STONE_PLATE = 70, + IRON_DOOR = 71, + WOOD_PLATE = 72, + REDSTONE_ORE = 73, + GLOWING_REDSTONE_ORE = 74, + REDSTONE_TORCH_OFF = 75, + REDSTONE_TORCH_ON = 76, + STONE_BUTTON = 77, + SNOW = 78, + ICE = 79, + SNOW_BLOCK = 80, + CACTUS = 81, + CLAY_BLOCK = 82, + SUGAR_CANE = 83, + JUKEBOX = 84, + FENCE = 85, + PUMPKIN = 86, + NETHERRACK = 87, + SOUL_SAND = 88, + GLOWSTONE_BLOCK = 89, + PORTAL = 90, + JACK_O_LANTERN = 91, + CAKE_BLOCK = 92, + REDSTONE_REPEATER_ON = 93, + REDSTONE_REPEATER_OFF = 94, + LOCKED_CHEST = 95, + } + + public class BlockInfo + { + public const int MAX_BLOCKS = 256; + + public const int MAX_OPACITY = 15; + public const int MIN_OPACITY = 0; + public const int MAX_LUMINANCE = 15; + public const int MIN_LUMINANCE = 0; + + private static BlockInfo[] _blockTable; + private static int[] _opacityTable; + private static int[] _luminanceTable; + + public class ItemCache + { + private T[] _cache; + + public T this[int index] + { + get { return _cache[index]; } + } + + public ItemCache (T[] cache) + { + _cache = cache; + } + } + + private class DataLimits + { + private int _low; + private int _high; + private int _bitmask; + + public int Low + { + get { return _low; } + } + + public int High + { + get { return _high; } + } + + public int Bitmask + { + get { return _bitmask; } + } + + public DataLimits (int low, int high, int bitmask) + { + _low = low; + _high = high; + _bitmask = bitmask; + } + + public bool Test (int data) + { + int rdata = data & ~_bitmask; + return rdata >= _low && rdata <= _high; + } + } + + private int _id = 0; + private string _name = ""; + private int _opacity = MAX_OPACITY; + private int _luminance = MIN_LUMINANCE; + + private DataLimits _dataLimits; + + public static ItemCache BlockTable; + + public static ItemCache OpacityTable; + + public static ItemCache LuminanceTable; + + public static ItemCache SchemaTable; + + public int ID + { + get { return _id; } + } + + public string Name + { + get { return _name; } + } + + public int Opacity + { + get { return _opacity; } + } + + public int Luminance + { + get { return _luminance; } + } + + public BlockInfo (int id) + { + _id = id; + _blockTable[_id] = this; + } + + public BlockInfo (int id, string name) + { + _id = id; + _name = name; + _blockTable[_id] = this; + } + + public BlockInfo SetOpacity (int opacity) + { + _opacity = MIN_OPACITY + opacity; + _opacityTable[_id] = _opacity; + return this; + } + + public BlockInfo SetLuminance (int luminance) + { + _luminance = luminance; + _luminanceTable[_id] = _luminance; + return this; + } + + public BlockInfo SetDataLimits (int low, int high, int bitmask) + { + _dataLimits = new DataLimits(low, high, bitmask); + return this; + } + + public bool TestData (int data) + { + if (_dataLimits == null) { + return true; + } + return _dataLimits.Test(data); + } + + public static BlockInfo Air; + public static BlockInfo Stone; + public static BlockInfo Grass; + public static BlockInfo Dirt; + public static BlockInfo Cobblestone; + public static BlockInfo WoodPlank; + public static BlockInfo Sapling; + public static BlockInfo Bedrock; + public static BlockInfo Water; + public static BlockInfo StationaryWater; + public static BlockInfo Lava; + public static BlockInfo StationaryLava; + public static BlockInfo Sand; + public static BlockInfo Gravel; + public static BlockInfo GoldOre; + public static BlockInfo IronOre; + public static BlockInfo CoalOre; + public static BlockInfo Wood; + public static BlockInfo Leaves; + public static BlockInfo Sponge; + public static BlockInfo Glass; + public static BlockInfo LapisOre; + public static BlockInfo LapisBlock; + public static BlockInfoEx Dispenser; + public static BlockInfo Sandstone; + public static BlockInfoEx NoteBlock; + public static BlockInfo Bed; + public static BlockInfo Wool; + public static BlockInfo YellowFlower; + public static BlockInfo RedRose; + public static BlockInfo BrownMushroom; + public static BlockInfo RedMushroom; + public static BlockInfo GoldBlock; + public static BlockInfo IronBlock; + public static BlockInfo DoubleSlab; + public static BlockInfo Slab; + public static BlockInfo BrickBlock; + public static BlockInfo TNT; + public static BlockInfo Bookshelf; + public static BlockInfo MossStone; + public static BlockInfo Obsidian; + public static BlockInfo Torch; + public static BlockInfo Fire; + public static BlockInfoEx MonsterSpawner; + public static BlockInfo WoodStairs; + public static BlockInfoEx Chest; + public static BlockInfo RedstoneWire; + public static BlockInfo DiamondOre; + public static BlockInfo DiamondBlock; + public static BlockInfo CraftTable; + public static BlockInfo Crops; + public static BlockInfo Farmland; + public static BlockInfoEx Furnace; + public static BlockInfoEx BurningFurnace; + public static BlockInfoEx SignPost; + public static BlockInfo WoodDoor; + public static BlockInfo Ladder; + public static BlockInfo Rails; + public static BlockInfo CobbleStairs; + public static BlockInfoEx WallSign; + public static BlockInfo Lever; + public static BlockInfo StonePlate; + public static BlockInfo IronDoor; + public static BlockInfo WoodPlate; + public static BlockInfo RedstoneOre; + public static BlockInfo GlowRedstoneOre; + public static BlockInfo RedstoneTorch; + public static BlockInfo RedstoneTorchOn; + public static BlockInfo StoneButton; + public static BlockInfo Snow; + public static BlockInfo Ice; + public static BlockInfo SnowBlock; + public static BlockInfo Cactus; + public static BlockInfo ClayBlock; + public static BlockInfo SugarCane; + public static BlockInfo Jukebox; + public static BlockInfo Fence; + public static BlockInfo Pumpkin; + public static BlockInfo Netherrack; + public static BlockInfo SoulSand; + public static BlockInfo Glowstone; + public static BlockInfo Portal; + public static BlockInfo JackOLantern; + public static BlockInfo CakeBlock; + public static BlockInfo RedstoneRepeater; + public static BlockInfo RedstoneRepeaterOn; + + static BlockInfo () + { + _blockTable = new BlockInfo[MAX_BLOCKS]; + _opacityTable = new int[MAX_BLOCKS]; + _luminanceTable = new int[MAX_BLOCKS]; + + BlockTable = new ItemCache(_blockTable); + OpacityTable = new ItemCache(_opacityTable); + LuminanceTable = new ItemCache(_luminanceTable); + + Air = new BlockInfo(0, "Air").SetOpacity(0); + Stone = new BlockInfo(1, "Stone"); + Grass = new BlockInfo(2, "Grass"); + Dirt = new BlockInfo(3, "Dirt"); + Cobblestone = new BlockInfo(4, "Cobblestone"); + WoodPlank = new BlockInfo(5, "Wooden Plank"); + Sapling = new BlockInfo(6, "Sapling").SetOpacity(0); + Bedrock = new BlockInfo(7, "Bedrock"); + Water = new BlockInfo(8, "Water").SetOpacity(3); + StationaryWater = new BlockInfo(9, "Stationary Water").SetOpacity(3); + Lava = new BlockInfo(10, "Lava").SetLuminance(MAX_LUMINANCE); + StationaryLava = new BlockInfo(11, "Stationary Lava").SetLuminance(MAX_LUMINANCE); + Sand = new BlockInfo(12, "Sand"); + Gravel = new BlockInfo(13, "Gravel"); + GoldOre = new BlockInfo(14, "Gold Ore"); + IronOre = new BlockInfo(15, "Iron Ore"); + CoalOre = new BlockInfo(16, "Coal Ore"); + Wood = new BlockInfo(17, "Wood"); + Leaves = new BlockInfo(18, "Leaves").SetOpacity(1); + Sponge = new BlockInfo(19, "Sponge"); + Glass = new BlockInfo(20, "Glass").SetOpacity(0); + LapisOre = new BlockInfo(21, "Lapis Lazuli Ore"); + LapisBlock = new BlockInfo(22, "Lapis Lazuli Block"); + Dispenser = new BlockInfoEx(23, "Dispenser"); + Sandstone = new BlockInfo(24, "Sandstone"); + NoteBlock = new BlockInfoEx(25, "Note Block"); + Bed = new BlockInfo(26, "Bed").SetOpacity(0); + Wool = new BlockInfo(35, "Wool"); + YellowFlower = new BlockInfo(37, "Yellow Flower").SetOpacity(0); + RedRose = new BlockInfo(38, "Red Rose").SetOpacity(0); + BrownMushroom = new BlockInfo(39, "Brown Mushroom").SetOpacity(0).SetLuminance(1); + RedMushroom = new BlockInfo(40, "Red Mushroom").SetOpacity(0).SetLuminance(1); + GoldBlock = new BlockInfo(41, "Gold Block"); + IronBlock = new BlockInfo(42, "Iron Block"); + DoubleSlab = new BlockInfo(43, "Double Slab"); + Slab = new BlockInfo(44, "Slab"); + BrickBlock = new BlockInfo(45, "Brick Block"); + TNT = new BlockInfo(46, "TNT"); + Bookshelf = new BlockInfo(47, "Bookshelf"); + MossStone = new BlockInfo(48, "Moss Stone"); + Obsidian = new BlockInfo(49, "Obsidian"); + Torch = new BlockInfo(50, "Torch").SetOpacity(0).SetLuminance(MAX_LUMINANCE - 1); + Fire = new BlockInfo(51, "Fire").SetOpacity(0).SetLuminance(MAX_LUMINANCE); + MonsterSpawner = (BlockInfoEx)new BlockInfoEx(52, "Monster Spawner").SetOpacity(0); + WoodStairs = new BlockInfo(53, "Wooden Stairs").SetOpacity(0); + Chest = new BlockInfoEx(54, "Chest"); + RedstoneWire = new BlockInfo(55, "Redstone Wire").SetOpacity(0); + DiamondOre = new BlockInfo(56, "Diamond Ore"); + DiamondBlock = new BlockInfo(57, "Diamond Block"); + CraftTable = new BlockInfo(58, "Crafting Table"); + Crops = new BlockInfo(59, "Crops").SetOpacity(0); + Farmland = new BlockInfo(60, "Farmland").SetOpacity(0); + Furnace = new BlockInfoEx(61, "Furnace"); + BurningFurnace = (BlockInfoEx)new BlockInfoEx(62, "Burning Furnace").SetLuminance(MAX_LUMINANCE - 1); + SignPost = (BlockInfoEx)new BlockInfoEx(63, "Sign Post").SetOpacity(0); + WoodDoor = new BlockInfo(64, "Wooden Door").SetOpacity(0); + Ladder = new BlockInfo(65, "Ladder").SetOpacity(0); + Rails = new BlockInfo(66, "Rails").SetOpacity(0); + CobbleStairs = new BlockInfo(67, "Cobblestone Stairs").SetOpacity(0); + WallSign = (BlockInfoEx)new BlockInfoEx(68, "Wall Sign").SetOpacity(0); + Lever = new BlockInfo(69, "Lever").SetOpacity(0); + StonePlate = new BlockInfo(70, "Stone Pressure Plate").SetOpacity(0); + IronDoor = new BlockInfo(71, "Iron Door").SetOpacity(0); + WoodPlate = new BlockInfo(72, "Wooden Pressure Plate").SetOpacity(0); + RedstoneOre = new BlockInfo(73, "Redstone Ore"); + GlowRedstoneOre = new BlockInfo(74, "Glowing Redstone Ore").SetLuminance(9); + RedstoneTorch = new BlockInfo(75, "Redstone Torch (Off)").SetOpacity(0); + RedstoneTorchOn = new BlockInfo(76, "Redstone Torch (On)").SetOpacity(0).SetLuminance(7); + StoneButton = new BlockInfo(77, "Stone Button").SetOpacity(0); + Snow = new BlockInfo(78, "Snow").SetOpacity(0); + Ice = new BlockInfo(79, "Ice").SetOpacity(3); + SnowBlock = new BlockInfo(80, "Snow Block"); + Cactus = new BlockInfo(81, "Cactus").SetOpacity(0); + ClayBlock = new BlockInfo(82, "Clay Block"); + SugarCane = new BlockInfo(83, "Sugar Cane").SetOpacity(0); + Jukebox = new BlockInfo(84, "Jukebox"); + Fence = new BlockInfo(85, "Fence").SetOpacity(0); + Pumpkin = new BlockInfo(86, "Pumpkin"); + Netherrack = new BlockInfo(87, "Netherrack"); + SoulSand = new BlockInfo(88, "Soul Sand"); + Glowstone = new BlockInfo(89, "Glowstone Block").SetLuminance(MAX_LUMINANCE); + Portal = new BlockInfo(90, "Portal").SetOpacity(0).SetLuminance(11); + JackOLantern = new BlockInfo(91, "Jack-O-Lantern").SetLuminance(MAX_LUMINANCE); + CakeBlock = new BlockInfo(92, "Cake Block").SetOpacity(0); + RedstoneRepeater = new BlockInfo(93, "Redstone Repeater (Off)").SetOpacity(0); + RedstoneRepeaterOn = new BlockInfo(94, "Redstone Repeater (On)").SetOpacity(0).SetLuminance(7); + + for (int i = 0; i < MAX_BLOCKS; i++) { + if (_blockTable[i] == null) { + _blockTable[i] = new BlockInfo(i, "Uknown Block"); + } + } + + // Set Tile Entity Data + + Dispenser.SetTileEntity("Trap"); + NoteBlock.SetTileEntity("Music"); + MonsterSpawner.SetTileEntity("MobSpawner"); + Chest.SetTileEntity("Chest"); + Furnace.SetTileEntity("Furnace"); + BurningFurnace.SetTileEntity("Furnace"); + SignPost.SetTileEntity("Sign"); + WallSign.SetTileEntity("Sign"); + + // Set Data Limits + + Wood.SetDataLimits(0, 2, 0); + Leaves.SetDataLimits(0, 2, 0); + Jukebox.SetDataLimits(0, 2, 0); + Sapling.SetDataLimits(0, 15, 0); + Cactus.SetDataLimits(0, 15, 0); + SugarCane.SetDataLimits(0, 15, 0); + Water.SetDataLimits(0, 7, 0x8); + Lava.SetDataLimits(0, 7, 0x8); + Crops.SetDataLimits(0, 7, 0); + Wool.SetDataLimits(0, 15, 0); + Torch.SetDataLimits(1, 5, 0); + RedstoneTorch.SetDataLimits(0, 5, 0); + RedstoneTorchOn.SetDataLimits(0, 5, 0); + Rails.SetDataLimits(0, 9, 0); + Ladder.SetDataLimits(2, 5, 0); + WoodStairs.SetDataLimits(0, 3, 0); + CobbleStairs.SetDataLimits(0, 3, 0); + Lever.SetDataLimits(0, 6, 0x8); + WoodDoor.SetDataLimits(0, 3, 0xC); + IronDoor.SetDataLimits(0, 3, 0xC); + StoneButton.SetDataLimits(1, 4, 0x8); + SignPost.SetDataLimits(0, 15, 0); + WallSign.SetDataLimits(2, 5, 0); + Furnace.SetDataLimits(2, 5, 0); + BurningFurnace.SetDataLimits(2, 5, 0); + Dispenser.SetDataLimits(2, 5, 0); + Pumpkin.SetDataLimits(0, 3, 0); + JackOLantern.SetDataLimits(0, 3, 0); + StonePlate.SetDataLimits(0, 0, 0x1); + WoodPlate.SetDataLimits(0, 0, 0x1); + Slab.SetDataLimits(0, 3, 0); + DoubleSlab.SetDataLimits(0, 3, 0); + Cactus.SetDataLimits(0, 5, 0); + Bed.SetDataLimits(0, 3, 0x8); + RedstoneRepeater.SetDataLimits(0, 0, 0xF); + RedstoneRepeaterOn.SetDataLimits(0, 0, 0xF); + } + } + + public class BlockInfoEx : BlockInfo + { + private string _tileEntityName; + + public string TileEntityName + { + get { return _tileEntityName; } + } + + public BlockInfoEx (int id) : base(id) { } + + public BlockInfoEx (int id, string name) : base(id, name) { } + + public BlockInfo SetTileEntity (string name) { + _tileEntityName = name; + return this; + } + } +} diff --git a/Substrate/SubstrateCS/Source/BlockInterface.cs b/Substrate/SubstrateCS/Source/BlockInterface.cs new file mode 100644 index 0000000..72cbb86 --- /dev/null +++ b/Substrate/SubstrateCS/Source/BlockInterface.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + + public interface IBlock + { + BlockInfo Info { get; } + int ID { get; set; } + int Data { get; set; } + int BlockLight { get; set; } + int SkyLight { get; set; } + + TileEntity GetTileEntity (); + bool SetTileEntity (TileEntity te); + bool ClearTileEntity (); + } + + public interface IBlockContainer + { + int BlockGlobalX (int x); + int BlockGlobalY (int y); + int BlockGlobalZ (int z); + + int BlockLocalX (int x); + int BlockLocalY (int y); + int BlockLocalZ (int z); + + Block GetBlock (int lx, int ly, int lz); + BlockRef GetBlockRef (int lx, int ly, int lz); + + BlockInfo GetBlockInfo (int lx, int ly, int lz); + + int GetBlockID (int lx, int ly, int lz); + int GetBlockData (int lx, int ly, int lz); + int GetBlockLight (int lx, int ly, int lz); + int GetBlockSkyLight (int lx, int ly, int lz); + + void SetBlock (int lx, int ly, int lz, Block block); + + bool SetBlockID (int lx, int ly, int lz, int id); + bool SetBlockData (int lx, int ly, int lz, int data); + bool SetBlockLight (int lx, int ly, int lz, int light); + bool SetBlockSkyLight (int lx, int ly, int lz, int light); + + TileEntity GetTileEntity (int lx, int ly, int lz); + bool SetTileEntity (int lx, int ly, int lz, TileEntity te); + bool ClearTileEntity (int lx, int ly, int lz); + } + + public interface IEntity + { + + } + + public interface IEntityCollection + { + List FindAll (Predicate match); + } +} diff --git a/Substrate/SubstrateCS/Source/BlockKey.cs b/Substrate/SubstrateCS/Source/BlockKey.cs new file mode 100644 index 0000000..5d58775 --- /dev/null +++ b/Substrate/SubstrateCS/Source/BlockKey.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + public struct BlockKey : IEquatable + { + readonly int x; + readonly int y; + readonly int z; + + public BlockKey (int _x, int _y, int _z) + { + x = _x; + y = _y; + z = _z; + } + + public bool Equals (BlockKey bk) + { + return this.x == bk.x && this.y == bk.y && this.z == bk.z; + } + + public override bool Equals (Object o) + { + try { + return this == (BlockKey)o; + } + catch { + return false; + } + } + + public override int GetHashCode () + { + int hash = 23; + hash = hash * 37 + x; + hash = hash * 37 + y; + hash = hash * 37 + z; + return hash; + } + + public static bool operator == (BlockKey k1, BlockKey k2) + { + return k1.x == k2.x && k1.y == k2.y && k1.z == k2.z; + } + + public static bool operator != (BlockKey k1, BlockKey k2) + { + return k1.x != k2.x || k1.y != k2.y || k1.z != k2.z; + } + } +} diff --git a/Substrate/SubstrateCS/Source/BlockManager.cs b/Substrate/SubstrateCS/Source/BlockManager.cs new file mode 100644 index 0000000..b4253e1 --- /dev/null +++ b/Substrate/SubstrateCS/Source/BlockManager.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + /*public interface IBlockManager : IBlockContainer + { + Block GetBlock (int x, int y, int z); + BlockRef GetBlockRef (int x, int y, int z); + BlockInfo GetBlockInfo (int x, int y, int z); + + bool SetBlock (int x, int y, int z, Block block); + }*/ + + public class BlockManager : IBlockContainer + { + public const int MIN_X = -32000000; + public const int MAX_X = 32000000; + public const int MIN_Y = 0; + public const int MAX_Y = 128; + public const int MIN_Z = -32000000; + public const int MAX_Z = 32000000; + + public const int CHUNK_XLEN = 16; + public const int CHUNK_YLEN = 128; + public const int CHUNK_ZLEN = 16; + + public const int CHUNK_XLOG = 4; + public const int CHUNK_YLOG = 7; + public const int CHUNK_ZLOG = 4; + + public const int CHUNK_XMASK = 0xF; + public const int CHUNK_YMASK = 0x7F; + public const int CHUNK_ZMASK = 0xF; + + public static bool EnforceDataLimits = true; + + protected ChunkManager _chunkMan; + + protected ChunkRef _cache; + + public BlockManager (ChunkManager cm) + { + _chunkMan = cm; + } + + public BlockManager (BlockManager bm) + { + _chunkMan = bm._chunkMan; + } + + public int BlockGlobalX (int x) + { + return x; + } + + public int BlockGlobalY (int y) + { + return y; + } + + public int BlockGlobalZ (int z) + { + return z; + } + + public int BlockLocalX (int x) + { + return x & CHUNK_XMASK; + } + + public int BlockLocalY (int y) + { + return y & CHUNK_YMASK; + } + + public int BlockLocalZ (int z) + { + return z & CHUNK_ZMASK; + } + + public virtual Block GetBlock (int x, int y, int z) + { + _cache = GetChunk(x, y, z); + if (_cache == null || !Check(x, y, z)) { + return null; + } + + return _cache.GetBlock(x & CHUNK_XMASK, y, z & CHUNK_ZMASK); + } + + public virtual BlockRef GetBlockRef (int x, int y, int z) + { + _cache = GetChunk(x, y, z); + if (_cache == null || !Check(x, y, z)) { + return null; + } + + return _cache.GetBlockRef(x & CHUNK_XMASK, y, z & CHUNK_ZMASK); + } + + public virtual BlockInfo GetBlockInfo (int x, int y, int z) + { + _cache = GetChunk(x, y, z); + if (_cache == null || !Check(x, y, z)) { + return null; + } + + return _cache.GetBlockInfo(x & CHUNK_XMASK, y, z & CHUNK_ZMASK); + } + + public virtual int GetBlockID (int x, int y, int z) + { + _cache = GetChunk(x, y, z); + if (_cache == null) { + return 0; + } + + return _cache.GetBlockID(x & CHUNK_XMASK, y, z & CHUNK_ZMASK); + } + + public virtual int GetBlockData (int x, int y, int z) + { + _cache = GetChunk(x, y, z); + if (_cache == null) { + return 0; + } + + return _cache.GetBlockData(x & CHUNK_XMASK, y, z & CHUNK_ZMASK); + } + + public virtual int GetBlockLight (int x, int y, int z) + { + _cache = GetChunk(x, y, z); + if (_cache == null) { + return 0; + } + + return _cache.GetBlockLight(x & CHUNK_XMASK, y, z & CHUNK_ZMASK); + } + + public virtual int GetBlockSkyLight (int x, int y, int z) + { + _cache = GetChunk(x, y, z); + if (_cache == null) { + return 0; + } + + return _cache.GetBlockSkyLight(x & CHUNK_XMASK, y, z & CHUNK_ZMASK); + } + + public virtual void SetBlock (int x, int y, int z, Block block) + { + _cache = GetChunk(x, y, z); + if (_cache == null || !Check(x, y, z)) { + return; + } + + _cache.SetBlock(x & CHUNK_XMASK, y, z & CHUNK_ZMASK, block); + } + + public virtual bool SetBlockID (int x, int y, int z, int id) + { + _cache = GetChunk(x, y, z); + if (_cache == null || !Check(x, y, z)) { + return false; + } + + return _cache.SetBlockID(x & CHUNK_XMASK, y, z & CHUNK_ZMASK, id); + } + + public virtual bool SetBlockData (int x, int y, int z, int data) + { + _cache = GetChunk(x, y, z); + if (_cache == null || !Check(x, y, z)) { + return false; + } + + return _cache.SetBlockData(x & CHUNK_XMASK, y, z & CHUNK_ZMASK, data); + } + + public bool SetBlockLight (int x, int y, int z, int light) + { + _cache = GetChunk(x, y, z); + if (_cache == null || !Check(x, y, z)) { + return false; + } + + return _cache.SetBlockID(x & CHUNK_XMASK, y, z & CHUNK_ZMASK, light); + } + + public bool SetBlockSkyLight (int x, int y, int z, int light) + { + _cache = GetChunk(x, y, z); + if (_cache == null || !Check(x, y, z)) { + return false; + } + + return _cache.SetBlockSkyLight(x & CHUNK_XMASK, y, z & CHUNK_ZMASK, light); + } + + public virtual TileEntity GetTileEntity (int x, int y, int z) + { + _cache = GetChunk(x, y, z); + if (_cache == null || !Check(x, y, z)) { + return null; + } + + return _cache.GetTileEntity(x & CHUNK_XMASK, y, z & CHUNK_ZMASK); + } + + public virtual bool SetTileEntity (int x, int y, int z, TileEntity te) + { + _cache = GetChunk(x, y, z); + if (_cache == null || !Check(x, y, z)) { + return false; + } + + return _cache.SetTileEntity(x & CHUNK_XMASK, y, z & CHUNK_ZMASK, te); + } + + public virtual bool ClearTileEntity (int x, int y, int z) + { + _cache = GetChunk(x, y, z); + if (_cache == null || !Check(x, y, z)) { + return false; + } + + return _cache.ClearTileEntity(x & CHUNK_XMASK, y, z & CHUNK_ZMASK); + } + + protected ChunkRef GetChunk (int x, int y, int z) + { + x >>= CHUNK_XLOG; + z >>= CHUNK_ZLOG; + return _chunkMan.GetChunkRef(x, z); + } + + /// + /// Called by other block-specific 'get' and 'set' functions to filter + /// out operations on some blocks. Override this method in derrived + /// classes to filter the entire BlockManager. + /// + protected virtual bool Check (int x, int y, int z) { + return (x >= MIN_X) && (x < MAX_X) && + (y >= MIN_Y) && (y < MAX_Y) && + (z >= MIN_Z) && (z < MAX_Z); + } + } +} diff --git a/Substrate/SubstrateCS/Source/BlockRef.cs b/Substrate/SubstrateCS/Source/BlockRef.cs new file mode 100644 index 0000000..eaaa198 --- /dev/null +++ b/Substrate/SubstrateCS/Source/BlockRef.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + public class BlockRef : IBlock + { + protected IBlockContainer _container; + + protected int _x; + protected int _y; + protected int _z; + + public int X + { + get { return _container.BlockGlobalX(_x); } + } + + public int Y + { + get { return _container.BlockGlobalY(_y); } + } + + public int Z + { + get { return _container.BlockGlobalZ(_z); } + } + + public int LocalX + { + get { return _container.BlockLocalX(_x); } + } + + public int LocalY + { + get { return _container.BlockLocalZ(_z); } + } + + public int LocalZ + { + get { return _z; } + } + + public BlockInfo Info + { + get { return BlockInfo.BlockTable[_container.GetBlockID(_x, _y, _z)]; } + } + + public int ID + { + get { return _container.GetBlockID(_x, _y, _z); } + set { _container.SetBlockID(_x, _y, _z, value); } + } + + public int Data + { + get { return _container.GetBlockData(_x, _y, _z); } + set { _container.SetBlockData(_x, _y, _z, value); } + } + + public int BlockLight + { + get { return _container.GetBlockLight(_x, _y, _z); } + set { _container.SetBlockLight(_x, _y, _z, value); } + } + + public int SkyLight + { + get { return _container.GetBlockSkyLight(_x, _y, _z); } + set { _container.SetBlockSkyLight(_x, _y, _z, value); } + } + + public BlockRef (IBlockContainer container, int x, int y, int z) + { + _container = container; + _x = x; + _y = y; + _z = z; + } + + public void CopyFrom (IBlock block) + { + ID = block.ID; + Data = block.Data; + BlockLight = block.BlockLight; + SkyLight = block.SkyLight; + + SetTileEntity(block.GetTileEntity().Copy()); + } + + public TileEntity GetTileEntity () + { + return _container.GetTileEntity(_x, _y, _z); + } + + public bool SetTileEntity (TileEntity te) + { + return _container.SetTileEntity(_x, _y, _z, te); + } + + public bool ClearTileEntity () + { + return _container.ClearTileEntity(_x, _y, _z); + } + } + + /*public class BlockRef : IBlock + { + protected IChunk _chunk; + + protected int _lx; + protected int _ly; + protected int _lz; + + public int X + { + get { return _lx + (_chunk.X * BlockManager.CHUNK_XLEN); } + } + + public int Y + { + get { return _ly; } + } + + public int Z + { + get { return _lz + (_chunk.Z * BlockManager.CHUNK_ZLEN); } + } + + public int LocalX + { + get { return _lx; } + } + + public int LocalY + { + get { return _ly; } + } + + public int LocalZ + { + get { return _lz; } + } + + public BlockInfo Info + { + get { return BlockInfo.BlockTable[_chunk.GetBlockID(_lx, _ly, _lz)]; } + } + + public int ID + { + get { return _chunk.GetBlockID(_lx, _ly, _lz); } + set { _chunk.SetBlockID(_lx, _ly, _lz, value); } + } + + public int Data + { + get { return _chunk.GetBlockData(_lx, _ly, _lz); } + set { _chunk.SetBlockData(_lx, _ly, _lz, value); } + } + + public int BlockLight + { + get { return _chunk.GetBlockLight(_lx, _ly, _lz); } + set { _chunk.SetBlockLight(_lx, _ly, _lz, value); } + } + + public int SkyLight + { + get { return _chunk.GetBlockSkyLight(_lx, _ly, _lz); } + set { _chunk.SetBlockSkyLight(_lx, _ly, _lz, value); } + } + + public BlockRef (IChunk c, int lx, int ly, int lz) + { + _chunk = c; + _lx = lx; + _ly = ly; + _lz = lz; + } + + public void CopyFrom (IBlock block) + { + ID = block.ID; + Data = block.Data; + BlockLight = block.BlockLight; + SkyLight = block.SkyLight; + } + + public TileEntity GetTileEntity () + { + return _chunk.GetTileEntity(_lx, _ly, _lz); + } + + public bool SetTileEntity (TileEntity te) + { + return _chunk.SetTileEntity(_lx, _ly, _lz, te); + } + + public bool ClearTileEntity () + { + return _chunk.ClearTileEntity(_lx, _ly, _lz); + } + }*/ +} diff --git a/Substrate/SubstrateCS/Source/Chunk.cs b/Substrate/SubstrateCS/Source/Chunk.cs new file mode 100644 index 0000000..5b1e8fd --- /dev/null +++ b/Substrate/SubstrateCS/Source/Chunk.cs @@ -0,0 +1,524 @@ +using System; +using System.IO; + +namespace NBToolkit.Map +{ + using NBT; + using Utility; + using System.Collections.Generic; + + public class Chunk : IChunk, INBTObject, ICopyable + { + public static NBTCompoundNode LevelSchema = new NBTCompoundNode() + { + new NBTCompoundNode("Level") + { + new NBTArrayNode("Blocks", 32768), + new NBTArrayNode("Data", 16384), + new NBTArrayNode("SkyLight", 16384), + new NBTArrayNode("BlockLight", 16384), + new NBTArrayNode("HeightMap", 256), + new NBTListNode("Entities", NBT_Type.TAG_COMPOUND), + new NBTListNode("TileEntities", NBT_Type.TAG_COMPOUND, TileEntity.BaseSchema), + new NBTScalerNode("LastUpdate", NBT_Type.TAG_LONG), + new NBTScalerNode("xPos", NBT_Type.TAG_INT), + new NBTScalerNode("zPos", NBT_Type.TAG_INT), + new NBTScalerNode("TerrainPopulated", NBT_Type.TAG_BYTE), + }, + }; + + private NBT_Tree _tree; + + private int _cx; + private int _cz; + + protected NBT_ByteArray _blocks; + protected NibbleArray _data; + protected NibbleArray _blockLight; + protected NibbleArray _skyLight; + protected NBT_ByteArray _heightMap; + + protected NBT_List _entities; + protected NBT_List _tileEntities; + + protected Dictionary _tileEntityTable; + + public int X + { + get { return _cx; } + } + + public int Z + { + get { return _cz; } + } + + public NBT_Tree Tree + { + get { return _tree; } + } + + public bool IsTerrainPopulated + { + get { return _tree.Root["Level"].ToNBTCompound()["TerrainPopulated"].ToNBTByte() == 1; } + set { _tree.Root["Level"].ToNBTCompound()["TerrainPopulated"].ToNBTByte().Data = (byte)(value ? 1 : 0); } + } + + public Chunk (int x, int z) + { + _cx = x; + _cz = z; + + BuildNBTTree(); + + BuildTileEntityCache(); + } + + public Chunk (NBT_Tree tree) + { + if (LoadTreeSafe(tree.Root) == null) { + throw new InvalidNBTObjectException(); + } + } + + private void BuildNBTTree () + { + int elements2 = BlockManager.CHUNK_XLEN * BlockManager.CHUNK_ZLEN; + int elements3 = elements2 * BlockManager.CHUNK_YLEN; + + _blocks = new NBT_ByteArray(new byte[elements3]); + NBT_ByteArray data = new NBT_ByteArray(new byte[elements3 >> 1]); + NBT_ByteArray blocklight = new NBT_ByteArray(new byte[elements3 >> 1]); + NBT_ByteArray skylight = new NBT_ByteArray(new byte[elements3 >> 1]); + _heightMap = new NBT_ByteArray(new byte[elements2]); + _entities = new NBT_List(NBT_Type.TAG_COMPOUND); + _tileEntities = new NBT_List(NBT_Type.TAG_COMPOUND); + + _data = new NibbleArray(data.Data); + _blockLight = new NibbleArray(blocklight.Data); + _skyLight = new NibbleArray(skylight.Data); + + NBT_Compound level = new NBT_Compound(); + level.Add("Blocks", _blocks); + level.Add("Data", data); + level.Add("SkyLight", blocklight); + level.Add("BlockLight", skylight); + level.Add("HeightMap", _heightMap); + level.Add("Entities", _entities); + level.Add("TileEntities", _tileEntities); + level.Add("LastUpdate", new NBT_Long()); + level.Add("xPos", new NBT_Int()); + level.Add("zPos", new NBT_Int()); + level.Add("TerrainPopulated", new NBT_Byte()); + + _tree = new NBT_Tree(); + _tree.Root.Add("Level", level); + } + + public int BlockGlobalX (int x) + { + return _cx * BlockManager.CHUNK_XLEN + x; + } + + public int BlockGlobalY (int y) + { + return y; + } + + public int BlockGlobalZ (int z) + { + return _cz * BlockManager.CHUNK_ZLEN + z; + } + + public int BlockLocalX (int x) + { + return x; + } + + public int BlockLocalY (int y) + { + return y; + } + + public int BlockLocalZ (int z) + { + return z; + } + + public bool Save (Stream outStream) + { + if (outStream == null || !outStream.CanWrite) { + return false; + } + + _tree.WriteTo(outStream); + outStream.Close(); + + return true; + } + + public Block GetBlock (int lx, int ly, int lz) + { + return new Block(this, lx, ly, lz); + } + + public BlockRef GetBlockRef (int lx, int ly, int lz) + { + return new BlockRef(this, lx, ly, lz); + } + + public BlockInfo GetBlockInfo (int lx, int ly, int lz) + { + return BlockInfo.BlockTable[GetBlockID(lx, ly, lz)]; + } + + public void SetBlock (int lx, int ly, int lz, Block block) + { + int index = lx << 11 | lz << 7 | ly; + + SetBlockID(lx, ly, lz, block.ID); + SetBlockData(lx, ly, lz, block.Data); + + _blockLight[index] = block.BlockLight; + _skyLight[index] = block.SkyLight; + + SetTileEntity(lx, ly, lz, block.GetTileEntity().Copy()); + } + + public int GetBlockID (int lx, int ly, int lz) + { + return _blocks.Data[lx << 11 | lz << 7 | ly]; + } + + public int GetBlockData (int lx, int ly, int lz) + { + return _data[lx << 11 | lz << 7 | ly]; + } + + public int GetBlockLight (int lx, int ly, int lz) + { + return _blockLight[lx << 11 | lz << 7 | ly]; + } + + public int GetBlockSkyLight (int lx, int ly, int lz) + { + return _skyLight[lx << 11 | lz << 7 | ly]; + } + + public bool SetBlockID (int lx, int ly, int lz, int id) + { + int index = lx << 11 | lz << 7 | ly; + int oldid = _blocks.Data[index]; + + if (oldid == id) { + return false; + } + + // Update value + + _blocks.Data[index] = (byte)id; + + // Update tile entities + + BlockInfoEx info1 = BlockInfo.BlockTable[oldid] as BlockInfoEx; + BlockInfoEx info2 = BlockInfo.BlockTable[id] as BlockInfoEx; + + if (info1 != info2) { + if (info1 != null) { + ClearTileEntity(lx, ly, lz); + } + + if (info2 != null) { + CreateTileEntity(lx, ly, lz); + } + } + + /*if (BlockInfo.SchemaTable[_blocks[index]] != BlockInfo.SchemaTable[id]) { + if (BlockInfo.SchemaTable[_blocks[index]] != null) { + ClearTileEntity(lx, ly, lz); + } + + if (BlockInfo.SchemaTable[id] != null) { + TileEntity te = new TileEntity(BlockInfo.SchemaTable[id]); + te.X = BlockGlobalX(lx); + te.Y = BlockGlobalY(ly); + te.Z = BlockGlobalZ(lz); + _tileEntities.Add(te.Root); + } + }*/ + + // Update height map + + if (BlockInfo.BlockTable[id] != null) { + int tileHeight = GetHeight(lx, lz); + int newOpacity = BlockInfo.BlockTable[id].Opacity; + + if (ly > tileHeight && newOpacity > BlockInfo.MIN_OPACITY) { + _heightMap[lz << 4 | lx] = (byte)ly; + } + else if (ly == tileHeight && newOpacity == BlockInfo.MIN_OPACITY) { + for (int i = ly - 1; i >= 0; i--) { + if (BlockInfo.BlockTable[GetBlockID(lx, i, lz)].Opacity > BlockInfo.MIN_OPACITY) { + _heightMap[lz << 4 | lx] = (byte)i; + break; + } + } + } + } + + return true; + } + + public bool SetBlockData (int lx, int ly, int lz, int data) + { + int index = lx << 11 | lz << 7 | ly; + if (_data[index] == data) { + return false; + } + + if (BlockManager.EnforceDataLimits && BlockInfo.BlockTable[_blocks[index]] != null) { + if (!BlockInfo.BlockTable[_blocks[index]].TestData(data)) { + return false; + } + } + + _data[index] = data; + return true; + } + + public bool SetBlockLight (int lx, int ly, int lz, int light) + { + int index = lx << 11 | lz << 7 | ly; + if (_blockLight[index] == light) { + return false; + } + + _blockLight[index] = light; + return true; + } + + public bool SetBlockSkyLight (int lx, int ly, int lz, int light) + { + int index = lx << 11 | lz << 7 | ly; + if (_skyLight[index] == light) { + return false; + } + + _skyLight[index] = light; + return true; + } + + public int CountBlockID (int id) + { + int c = 0; + for (int i = 0; i < _blocks.Length; i++) { + if (_blocks[i] == id) { + c++; + } + } + + return c; + } + + public int CountBlockData (int id, int data) + { + int c = 0; + for (int i = 0; i < _blocks.Length; i++) { + if (_blocks[i] == id && _data[i] == data) { + c++; + } + } + + return c; + } + + public int GetHeight (int lx, int lz) + { + return _heightMap[lz << 4 | lx]; + } + + private void CreateTileEntity (int lx, int ly, int lz) + { + BlockInfoEx info = GetBlockInfo(lx, ly, lz) as BlockInfoEx; + if (info == null) { + return; + } + + TileEntity te = TileEntityFactory.Create(info.TileEntityName); + if (te == null) { + return; + } + + te.X = BlockGlobalX(lx); + te.Y = BlockGlobalY(ly); + te.Z = BlockGlobalZ(lz); + + _tileEntities.Add(te.BuildTree()); + } + + public TileEntity GetTileEntity (int lx, int ly, int lz) + { + int x = BlockGlobalX(lx); + int y = BlockGlobalY(ly); + int z = BlockGlobalZ(lz); + + BlockKey key = new BlockKey(x, y, z); + NBT_Compound te; + + if (!_tileEntityTable.TryGetValue(key, out te)) { + return null; + } + + return TileEntityFactory.Create(te); + } + + public bool SetTileEntity (int lx, int ly, int lz, TileEntity te) + { + BlockInfoEx info = GetBlockInfo(lx, ly, lz) as BlockInfoEx; + if (info == null) { + return false; + } + + if (te.GetType() != TileEntityFactory.Lookup(info.TileEntityName)) { + return false; + } + + int x = BlockGlobalX(lx); + int y = BlockGlobalY(ly); + int z = BlockGlobalZ(lz); + + BlockKey key = new BlockKey(x, y, z); + NBT_Compound oldte; + + if (_tileEntityTable.TryGetValue(key, out oldte)) { + _tileEntities.Remove(oldte); + } + + te.X = x; + te.Y = y; + te.Z = z; + + NBT_Compound tree = te.BuildTree() as NBT_Compound; + + _tileEntities.Add(tree); + _tileEntityTable[key] = tree; + + return true; + } + + public bool ClearTileEntity (int lx, int ly, int lz) + { + int x = BlockGlobalX(lx); + int y = BlockGlobalY(ly); + int z = BlockGlobalZ(lz); + + BlockKey key = new BlockKey(x, y, z); + NBT_Compound te; + + if (!_tileEntityTable.TryGetValue(key, out te)) { + return false; + } + + _tileEntities.Remove(te); + _tileEntityTable.Remove(key); + + return true; + } + + public virtual void SetLocation (int x, int z) + { + int diffx = x - _cx; + int diffz = z - _cz; + + _cx = x; + _cz = z; + + BuildTileEntityCache(); + } + + private void BuildTileEntityCache () + { + _tileEntityTable = new Dictionary(); + + foreach (NBT_Compound te in _tileEntities) { + int tex = te["x"].ToNBTInt(); + int tey = te["y"].ToNBTInt(); + int tez = te["z"].ToNBTInt(); + + BlockKey key = new BlockKey(tex, tey, tez); + _tileEntityTable[key] = te; + } + } + + #region ICopyable Members + + public Chunk Copy () + { + return new Chunk(_tree.Copy()); + } + + #endregion + + #region INBTObject Members + + public Chunk LoadTree (NBT_Value tree) + { + NBT_Compound ctree = tree as NBT_Compound; + if (ctree == null) { + return null; + } + + _tree = new NBT_Tree(ctree); + + NBT_Compound level = _tree.Root["Level"] as NBT_Compound; + + _blocks = level["Blocks"] as NBT_ByteArray; + _data = new NibbleArray(level["Data"].ToNBTByteArray().Data); + _blockLight = new NibbleArray(level["BlockLight"].ToNBTByteArray().Data); + _skyLight = new NibbleArray(level["SkyLight"].ToNBTByteArray().Data); + _heightMap = level["HeightMap"] as NBT_ByteArray; + + _entities = level["Entities"] as NBT_List; + _tileEntities = level["TileEntities"] as NBT_List; + + // List-type patch up + if (_entities.Count == 0) { + level["Entities"] = new NBT_List(NBT_Type.TAG_COMPOUND); + _entities = level["Entities"] as NBT_List; + } + + if (_tileEntities.Count == 0) { + level["TileEntities"] = new NBT_List(NBT_Type.TAG_COMPOUND); + _tileEntities = level["TileEntities"] as NBT_List; + } + + _cx = level["xPos"].ToNBTInt(); + _cz = level["zPos"].ToNBTInt(); + + BuildTileEntityCache(); + + return this; + } + + public Chunk LoadTreeSafe (NBT_Value tree) + { + if (!ValidateTree(tree)) { + return null; + } + + return LoadTree(tree); + } + + public NBT_Value BuildTree () + { + return _tree.Root; + } + + public bool ValidateTree (NBT_Value tree) + { + return new NBTVerifier(tree, LevelSchema).Verify(); + } + + #endregion + } +} diff --git a/Substrate/SubstrateCS/Source/ChunkEnumerator.cs b/Substrate/SubstrateCS/Source/ChunkEnumerator.cs new file mode 100644 index 0000000..d2d804d --- /dev/null +++ b/Substrate/SubstrateCS/Source/ChunkEnumerator.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.IO; + +namespace NBToolkit.Map +{ + public class ChunkList : IEnumerable + { + //private List _regions; + + protected ChunkManager _cm = null; + protected Region _region = null; + + // Constructor to enumerate a single region + public ChunkList (ChunkManager cm, Region region) + { + _cm = cm; + _region = region; + } + + // Constructor to enumerate all regions + public ChunkList (ChunkManager cm) + { + _cm = cm; + } + + IEnumerator IEnumerable.GetEnumerator () + { + return (IEnumerator)GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator () + { + return (IEnumerator)GetEnumerator(); + } + + public virtual ChunkEnumerator GetEnumerator () + { + return new ChunkEnumerator(_cm, _region); + } + } + + public class ChunkEnumerator : IEnumerator + { + protected Region _region; + protected ChunkManager _cm; + + protected ChunkRef _chunk; + + protected RegionEnumerator _enum = null; + protected int _x = 0; + protected int _z = -1; + + public ChunkEnumerator (ChunkManager cm, Region region) + { + _cm = cm; + _region = region; + + if (_region == null) { + _enum = new RegionEnumerator(_cm.GetRegionManager()); + _enum.MoveNext(); + _region = _enum.Current; + } + } + + public ChunkEnumerator (ChunkManager cm) + { + _cm = cm; + _enum = new RegionEnumerator(_cm.GetRegionManager()); + _enum.MoveNext(); + _region = _enum.Current; + } + + public virtual bool MoveNext () + { + if (_enum == null) { + return MoveNextInRegion(); + } + else { + while (true) { + if (_x >= ChunkManager.REGION_XLEN) { + if (!_enum.MoveNext()) { + return false; + } + _x = 0; + _z = -1; + _region = _enum.Current; + } + if (MoveNextInRegion()) { + _chunk = _cm.GetChunkRefInRegion(_region, _x, _z); + return true; + } + } + } + } + + protected bool MoveNextInRegion () + { + for (; _x < ChunkManager.REGION_XLEN; _x++) { + for (_z++; _z < ChunkManager.REGION_ZLEN; _z++) { + if (_region.ChunkExists(_x, _z)) { + goto FoundNext; + } + } + _z = -1; + } + + FoundNext: + + return (_x < ChunkManager.REGION_XLEN); + } + + public void Reset () + { + if (_enum != null) { + _enum.Reset(); + _enum.MoveNext(); + _region = _enum.Current; + } + _x = 0; + _z = -1; + } + + void IDisposable.Dispose () { } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + ChunkRef IEnumerator.Current + { + get + { + return Current; + } + } + + public ChunkRef Current + { + get + { + if (_x >= ChunkManager.REGION_XLEN) { + throw new InvalidOperationException(); + } + return _chunk; + } + } + } +} diff --git a/Substrate/SubstrateCS/Source/ChunkFile.cs b/Substrate/SubstrateCS/Source/ChunkFile.cs new file mode 100644 index 0000000..ef213fb --- /dev/null +++ b/Substrate/SubstrateCS/Source/ChunkFile.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using Ionic.Zlib; + +namespace NBToolkit.Map +{ + class ChunkFile + { + private string _filename; + + public ChunkFile (string path) + { + _filename = path; + } + + public ChunkFile (string path, int cx, int cz) + { + string cx64 = Base64(cx); + string cz64 = Base64(cz); + string file = "c." + cx64 + "." + cz64 + ".dat"; + + string dir1 = Base64(cx % 64); + string dir2 = Base64(cz % 64); + + _filename = Path.Combine(path, dir1); + _filename = Path.Combine(_filename, dir2); + _filename = Path.Combine(_filename, file); + } + + private string Base64 (int val) + { + return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(val.ToString())); + } + + public bool Exists () + { + return File.Exists(_filename); + } + + public bool Delete () + { + File.Delete(_filename); + return true; + } + + public Stream GetChunkDataInputStream () + { + FileStream fstr = new FileStream(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + long length = fstr.Seek(0, SeekOrigin.End); + fstr.Seek(0, SeekOrigin.Begin); + + byte[] data = new byte[length]; + fstr.Read(data, 0, data.Length); + + fstr.Close(); + + return new GZipStream(new MemoryStream(data), CompressionMode.Decompress); + } + + public Stream GetChunkDataOutputStream () + { + return new ZlibStream(new ChunkBuffer(this), CompressionMode.Compress); + } + + class ChunkBuffer : MemoryStream + { + private ChunkFile region; + + public ChunkBuffer (ChunkFile c) + : base(8096) + { + this.region = c; + } + + public override void Close () + { + FileStream fstr = new FileStream(region._filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); + fstr.Write(this.GetBuffer(), 0, (int)this.Length); + fstr.Close(); + } + } + + } +} diff --git a/Substrate/SubstrateCS/Source/ChunkFileManager.cs b/Substrate/SubstrateCS/Source/ChunkFileManager.cs new file mode 100644 index 0000000..e8320eb --- /dev/null +++ b/Substrate/SubstrateCS/Source/ChunkFileManager.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace NBToolkit.Map +{ + using NBT; + + class ChunkFileManager : IChunkContainer, IChunkCache + { + protected string _mapPath; + + protected Dictionary _cache; + protected Dictionary _dirty; + + public ChunkFileManager (string mapDir) + { + _mapPath = mapDir; + _cache = new Dictionary(); + _dirty = new Dictionary(); + } + + protected ChunkFile GetChunkFile (int cx, int cz) + { + return new ChunkFile(_mapPath, cx, cz); + } + + protected NBT_Tree GetChunkTree (int cx, int cz) + { + ChunkFile cf = GetChunkFile(cx, cz); + Stream nbtstr = cf.GetChunkDataInputStream(); + if (nbtstr == null) { + return null; + } + + return new NBT_Tree(nbtstr); + } + + protected bool SaveChunkTree (int cx, int cz, NBT_Tree tree) + { + ChunkFile cf = GetChunkFile(cx, cz); + Stream zipstr = cf.GetChunkDataOutputStream(); + if (zipstr == null) { + return false; + } + + tree.WriteTo(zipstr); + zipstr.Close(); + + return true; + } + + protected Stream GetChunkOutStream (int cx, int cz) + { + return new ChunkFile(_mapPath, cx, cz).GetChunkDataOutputStream(); + } + + #region IChunkContainer Members + + public int ChunkGlobalX (int cx) + { + return cx; + } + + public int ChunkGlobalZ (int cz) + { + return cz; + } + + public int ChunkLocalX (int cx) + { + return cx; + } + + public int ChunkLocalZ (int cz) + { + return cz; + } + + public Chunk GetChunk (int cx, int cz) + { + if (!ChunkExists(cx, cz)) { + return null; + } + + return new Chunk(GetChunkTree(cx, cz)); + } + + public ChunkRef GetChunkRef (int cx, int cz) + { + ChunkKey k = new ChunkKey(cx, cz); + + ChunkRef c = null; + + WeakReference chunkref = null; + if (_cache.TryGetValue(k, out chunkref)) { + c = chunkref.Target as ChunkRef; + } + else { + _cache.Add(k, new WeakReference(null)); + } + + if (c != null) { + return c; + } + + try { + c = new ChunkRef(this, this, cx, cz); + _cache[k].Target = c; + return c; + } + catch (MissingChunkException) { + return null; + } + } + + public bool ChunkExists (int cx, int cz) + { + return new ChunkFile(_mapPath, cx, cz).Exists(); + } + + public bool DeleteChunk (int cx, int cz) + { + new ChunkFile(_mapPath, cx, cz).Delete(); + + ChunkKey k = new ChunkKey(cx, cz); + _cache.Remove(k); + _dirty.Remove(k); + + return true; + } + + public int Save () + { + int saved = 0; + foreach (ChunkRef c in _dirty.Values) { + int cx = ChunkGlobalX(c.X); + int cz = ChunkGlobalZ(c.Z); + + if (c.Save(GetChunkOutStream(cx, cz))) { + saved++; + } + } + + _dirty.Clear(); + return saved; + } + + public bool SaveChunk (Chunk chunk) + { + return chunk.Save(GetChunkOutStream(ChunkGlobalX(chunk.X), ChunkGlobalZ(chunk.Z))); + } + + #endregion + + #region IChunkCache Members + + public bool MarkChunkDirty (ChunkRef chunk) + { + int cx = chunk.X; + int cz = chunk.Z; + + ChunkKey k = new ChunkKey(cx, cz); + if (!_dirty.ContainsKey(k)) { + _dirty.Add(k, GetChunkRef(cx, cz)); + return true; + } + return false; + } + + public bool MarkChunkClean (ChunkRef chunk) + { + int cx = chunk.X; + int cz = chunk.Z; + + ChunkKey k = new ChunkKey(cx, cz); + if (_dirty.ContainsKey(k)) { + _dirty.Remove(k); + return true; + } + return false; + } + + #endregion + + } +} diff --git a/Substrate/SubstrateCS/Source/ChunkInterface.cs b/Substrate/SubstrateCS/Source/ChunkInterface.cs new file mode 100644 index 0000000..9395af8 --- /dev/null +++ b/Substrate/SubstrateCS/Source/ChunkInterface.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace NBToolkit.Map +{ + + public interface IChunk : IBlockContainer + { + int X { get; } + int Z { get; } + + bool IsTerrainPopulated { get; set; } + + bool Save (Stream outStream); + + int CountBlockID (int id); + int CountBlockData (int id, int data); + + int GetHeight (int lx, int lz); + } + + public interface IChunkCache + { + bool MarkChunkDirty (ChunkRef chunk); + bool MarkChunkClean (ChunkRef chunk); + } + + public interface IChunkContainer + { + int ChunkGlobalX (int cx); + int ChunkGlobalZ (int cz); + + int ChunkLocalX (int cx); + int ChunkLocalZ (int cz); + + Chunk GetChunk (int cx, int cz); + ChunkRef GetChunkRef (int cx, int cz); + + bool ChunkExists (int cx, int cz); + + bool DeleteChunk (int cx, int cz); + + int Save (); + bool SaveChunk (Chunk chunk); + } +} diff --git a/Substrate/SubstrateCS/Source/ChunkKey.cs b/Substrate/SubstrateCS/Source/ChunkKey.cs new file mode 100644 index 0000000..4510705 --- /dev/null +++ b/Substrate/SubstrateCS/Source/ChunkKey.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + public struct ChunkKey : IEquatable + { + readonly int cx; + readonly int cz; + + public ChunkKey (int _cx, int _cz) + { + cx = _cx; + cz = _cz; + } + + public bool Equals (ChunkKey ck) + { + return this.cx == ck.cx && this.cz == ck.cz; + } + + public override bool Equals (Object o) + { + try { + return this == (ChunkKey)o; + } + catch { + return false; + } + } + + public override int GetHashCode () + { + int hash = 23; + hash = hash * 37 + cx; + hash = hash * 37 + cz; + return hash; + } + + public static bool operator == (ChunkKey k1, ChunkKey k2) + { + return k1.cx == k2.cx && k1.cz == k2.cz; + } + + public static bool operator != (ChunkKey k1, ChunkKey k2) + { + return k1.cx != k2.cx || k1.cz != k2.cz; + } + } +} diff --git a/Substrate/SubstrateCS/Source/ChunkManager.cs b/Substrate/SubstrateCS/Source/ChunkManager.cs new file mode 100644 index 0000000..a36affe --- /dev/null +++ b/Substrate/SubstrateCS/Source/ChunkManager.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + + public class ChunkManager : IChunkContainer, IChunkCache, IEnumerable + { + public const int REGION_XLEN = 32; + public const int REGION_ZLEN = 32; + + public const int REGION_XLOG = 5; + public const int REGION_ZLOG = 5; + + public const int REGION_XMASK = 0x1F; + public const int REGION_ZMASK = 0x1F; + + protected RegionManager _regionMan; + + protected Dictionary _cache; + protected Dictionary _dirty; + + public ChunkManager (RegionManager rm) + { + _regionMan = rm; + _cache = new Dictionary(); + _dirty = new Dictionary(); + } + + public int ChunkGlobalX (int cx) + { + return cx; + } + + public int ChunkGlobalZ (int cz) + { + return cz; + } + + public int ChunkLocalX (int cx) + { + return cx & REGION_XMASK; + } + + public int ChunkLocalZ (int cz) + { + return cz & REGION_ZMASK; + } + + public Chunk GetChunk (int cx, int cz) + { + Region r = GetRegion(cx, cz); + if (r == null) { + return null; + } + + return r.GetChunk(cx & REGION_XMASK, cz & REGION_ZMASK); + } + + public ChunkRef GetChunkRef (int cx, int cz) + { + Region r = GetRegion(cx, cz); + if (r == null) { + return null; + } + + return r.GetChunkRef(cx & REGION_XMASK, cz & REGION_ZMASK, this); + } + + public bool ChunkExists (int cx, int cz) + { + Region r = GetRegion(cx, cz); + if (r == null) { + return false; + } + + return r.ChunkExists(cx & REGION_XMASK, cz & REGION_ZMASK); + } + + public bool MarkChunkDirty (ChunkRef chunk) + { + Region r = GetRegion(chunk.X, chunk.Z); + if (r == null) { + return false; + } + + RegionKey k = new RegionKey(r.X, r.Z); + _dirty[k] = r; + + r.MarkChunkDirty(chunk); + + return true; + } + + public bool MarkChunkClean (ChunkRef chunk) + { + Region r = GetRegion(chunk.X, chunk.Z); + if (r == null) { + return false; + } + + RegionKey k = new RegionKey(r.X, r.Z); + _dirty.Remove(k); + + r.MarkChunkClean(chunk); + + return true; + } + + public int Save () + { + int saved = 0; + foreach (Region r in _dirty.Values) { + saved += r.Save(); + } + + _dirty.Clear(); + return saved; + } + + public bool SaveChunk (Chunk chunk) + { + Region r = GetRegion(chunk.X, chunk.Z); + if (r == null) { + return false; + } + + return r.SaveChunk(chunk); + } + + public bool DeleteChunk (int cx, int cz) + { + Region r = GetRegion(cx, cz); + if (r == null) { + return false; + } + + if (!r.DeleteChunk(cx & REGION_XMASK, cz & REGION_ZMASK)) { + return false; + } + + if (r.ChunkCount() == 0) { + RegionKey k = new RegionKey(r.X, r.Z); + _cache.Remove(k); + _dirty.Remove(k); + + _regionMan.DeleteRegion(r.X, r.Z); + } + + return true; + } + + public RegionManager GetRegionManager () + { + return _regionMan; + } + + public ChunkRef GetChunkRefInRegion (Region r, int lcx, int lcz) + { + int cx = r.X * REGION_XLEN + lcx; + int cz = r.Z * REGION_ZLEN + lcz; + return GetChunkRef(cx, cz); + } + + protected Region GetRegion (int cx, int cz) + { + cx >>= REGION_XLOG; + cz >>= REGION_ZLOG; + return _regionMan.GetRegion(cx, cz); + } + + #region IEnumerable Members + + public IEnumerator GetEnumerator () + { + return new ChunkEnumerator(this); + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () + { + return new ChunkEnumerator(this); + } + + #endregion + } + + public class MissingChunkException : Exception + { + + } +} diff --git a/Substrate/SubstrateCS/Source/ChunkRef.cs b/Substrate/SubstrateCS/Source/ChunkRef.cs new file mode 100644 index 0000000..d1dbbf5 --- /dev/null +++ b/Substrate/SubstrateCS/Source/ChunkRef.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace NBToolkit.Map +{ + using NBT; + + public class ChunkRef : IChunk + { + private IChunkContainer _container; + private IChunkCache _cache; + private Chunk _chunk; + + private int _cx; + private int _cz; + + private bool _dirty; + + public int X + { + get { return _container.ChunkGlobalX(_cx); } + } + + public int Z + { + get { return _container.ChunkGlobalZ(_cz); } + } + + public int LocalX + { + get { return _container.ChunkLocalX(_cx); } + } + + public int LocalZ + { + get { return _container.ChunkLocalZ(_cz); } + } + + public ChunkRef (IChunkContainer container, IChunkCache cache, int cx, int cz) + { + _container = container; + _cache = cache; + _cx = cx; + _cz = cz; + + if (!_container.ChunkExists(cx, cz)) { + throw new MissingChunkException(); + } + } + + public int BlockGlobalX (int x) + { + return _container.ChunkGlobalX(_cx) * BlockManager.CHUNK_XLEN + x; + } + + public int BlockGlobalY (int y) + { + return y; + } + + public int BlockGlobalZ (int z) + { + return _container.ChunkGlobalZ(_cz) * BlockManager.CHUNK_ZLEN + z; + } + + public int BlockLocalX (int x) + { + return x; + } + + public int BlockLocalY (int y) + { + return y; + } + + public int BlockLocalZ (int z) + { + return z; + } + + private Chunk GetChunk () + { + if (_chunk == null) { + _chunk = _container.GetChunk(_cx, _cz); + } + return _chunk; + } + + private bool MarkDirty () + { + if (_dirty) { + return false; + } + + _dirty = true; + _cache.MarkChunkDirty(this); + return true; + } + + public ChunkRef GetNorthNeighbor () + { + return _container.GetChunkRef(_cx - 1, _cz); + } + + public ChunkRef GetSouthNeighbor () + { + return _container.GetChunkRef(_cx + 1, _cz); + } + + public ChunkRef GetEastNeighbor () + { + return _container.GetChunkRef(_cx, _cz - 1); + } + + public ChunkRef GetWestNeighbor () + { + return _container.GetChunkRef(_cx, _cz + 1); + } + + public Chunk GetChunkCopy () + { + return GetChunk().Copy(); + } + + public Chunk GetChunkRef () + { + Chunk chunk = GetChunk(); + _chunk = null; + + return chunk; + } + + public void SetChunkRef (Chunk chunk) + { + _chunk = chunk; + _chunk.SetLocation(_cx, _cz); + MarkDirty(); + } + + #region IChunk Members + + public bool IsTerrainPopulated + { + get { return GetChunk().IsTerrainPopulated; } + set + { + if (GetChunk().IsTerrainPopulated != value) { + GetChunk().IsTerrainPopulated = value; + MarkDirty(); + } + } + } + + public bool Save (Stream outStream) + { + if (_dirty) { + if (GetChunk().Save(outStream)) { + _dirty = false; + return true; + } + return false; + } + return true; + } + + public Block GetBlock (int lx, int ly, int lz) + { + return new Block(this, lx, ly, lz); + } + + public BlockRef GetBlockRef (int lx, int ly, int lz) + { + return new BlockRef(this, lx, ly, lz); + } + + public BlockInfo GetBlockInfo (int lx, int ly, int lz) + { + return GetChunk().GetBlockInfo(lx, ly, lz); + } + + public void SetBlock (int lx, int ly, int lz, Block block) + { + GetChunk().SetBlock(lx, ly, lz, block); + } + + public int GetBlockID (int lx, int ly, int lz) + { + return GetChunk().GetBlockID(lx, ly, lz); + } + + public int GetBlockData (int lx, int ly, int lz) + { + return GetChunk().GetBlockData(lx, ly, lz); + } + + public int GetBlockLight (int lx, int ly, int lz) + { + return GetChunk().GetBlockSkyLight(lx, ly, lz); + } + + public int GetBlockSkyLight (int lx, int ly, int lz) + { + return GetChunk().GetBlockSkyLight(lx, ly, lz); + } + + public bool SetBlockID (int lx, int ly, int lz, int id) + { + if (GetChunk().SetBlockID(lx, ly, lz, id)) { + MarkDirty(); + return true; + } + return false; + } + + public bool SetBlockData (int lx, int ly, int lz, int data) + { + if (GetChunk().SetBlockData(lx, ly, lz, data)) { + MarkDirty(); + return true; + } + return false; + } + + public bool SetBlockLight (int lx, int ly, int lz, int light) + { + if (GetChunk().SetBlockLight(lx, ly, lz, light)) { + MarkDirty(); + return true; + } + return false; + } + + public bool SetBlockSkyLight (int lx, int ly, int lz, int light) + { + if (GetChunk().SetBlockSkyLight(lx, ly, lz, light)) { + MarkDirty(); + return true; + } + return false; + } + + public int CountBlockID (int id) + { + return GetChunk().CountBlockID(id); + } + + public int CountBlockData (int id, int data) + { + return GetChunk().CountBlockData(id, data); + } + + public int GetHeight (int lx, int lz) + { + return GetChunk().GetHeight(lx, lz); + } + + public TileEntity GetTileEntity (int lx, int ly, int lz) + { + return GetChunk().GetTileEntity(lx, ly, lz); + } + + public bool SetTileEntity (int lx, int ly, int lz, TileEntity te) + { + if (GetChunk().SetTileEntity(lx, ly, lz, te)) { + MarkDirty(); + return true; + } + return false; + } + + public bool ClearTileEntity (int lx, int ly, int lz) + { + if (GetChunk().ClearTileEntity(lx, ly, lz)) { + MarkDirty(); + return true; + } + return false; + } + + #endregion + } + + /*public bool VerifyTileEntities () + { + bool pass = true; + + NBT_List telist = GetTree().Root["Level"].ToNBTCompound()["TileEntities"].ToNBTList(); + + foreach (NBT_Value val in telist) { + NBT_Compound tree = val as NBT_Compound; + if (tree == null) { + pass = false; + continue; + } + + if (new NBTVerifier(tree, TileEntity.BaseSchema).Verify() == false) { + pass = false; + continue; + } + + int x = tree["x"].ToNBTInt() & BlockManager.CHUNK_XMASK; + int y = tree["y"].ToNBTInt() & BlockManager.CHUNK_YMASK; + int z = tree["z"].ToNBTInt() & BlockManager.CHUNK_ZMASK; + int id = GetBlockID(x, y, z); + + NBTCompoundNode schema = BlockInfo.SchemaTable[id]; + if (schema == null) { + pass = false; + continue; + } + + pass = new NBTVerifier(tree, schema).Verify() && pass; + } + + return pass; + } + + private static bool LocalBounds (int lx, int ly, int lz) + { + return lx >= 0 && lx < BlockManager.CHUNK_XLEN && + ly >= 0 && ly < BlockManager.CHUNK_YLEN && + lz >= 0 && lz < BlockManager.CHUNK_ZLEN; + }*/ + + public class MalformedNBTTreeException : Exception { } +} diff --git a/Substrate/SubstrateCS/Source/Entity.cs b/Substrate/SubstrateCS/Source/Entity.cs new file mode 100644 index 0000000..f449719 --- /dev/null +++ b/Substrate/SubstrateCS/Source/Entity.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + using NBT; + using Utility; + + public class Entity + { + private string _entityID; + private double _posX; + private double _posY; + private double _posZ; + private double _motionX; + private double _motionY; + private double _motionZ; + private float _rotationYaw; + private float _rotationPitch; + private float _fallDistance; + private short _fire; + private short _air; + private byte _onGround; + + + #region Predefined Schemas + + public static readonly NBTCompoundNode BaseSchema = new NBTCompoundNode("") + { + new NBTScalerNode("id", NBT_Type.TAG_STRING), + new NBTListNode("Pos", NBT_Type.TAG_DOUBLE, 3), + new NBTListNode("Motion", NBT_Type.TAG_DOUBLE, 3), + new NBTListNode("Rotation", NBT_Type.TAG_FLOAT, 2), + new NBTScalerNode("FallDistance", NBT_Type.TAG_FLOAT), + new NBTScalerNode("Fire", NBT_Type.TAG_SHORT), + new NBTScalerNode("Air", NBT_Type.TAG_SHORT), + new NBTScalerNode("OnGround", NBT_Type.TAG_BYTE), + }; + + public static readonly NBTCompoundNode MobSchema = BaseSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Mob"), + new NBTScalerNode("AttackTime", NBT_Type.TAG_SHORT), + new NBTScalerNode("DeathTime", NBT_Type.TAG_SHORT), + new NBTScalerNode("Health", NBT_Type.TAG_SHORT), + new NBTScalerNode("HurtTime", NBT_Type.TAG_SHORT), + }); + + public static readonly NBTCompoundNode MonsterSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Monster"), + }); + + public static readonly NBTCompoundNode CreeperSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Creeper"), + }); + + public static readonly NBTCompoundNode SkeletonSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Skeleton"), + }); + + public static readonly NBTCompoundNode SpiderSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Spider"), + }); + + public static readonly NBTCompoundNode GiantSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Giant"), + }); + + public static readonly NBTCompoundNode ZombieSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Zombie"), + }); + + public static readonly NBTCompoundNode PigZombieSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "PigZombie"), + }); + + public static readonly NBTCompoundNode GhastSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Ghast"), + }); + + public static readonly NBTCompoundNode PigSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Pig"), + new NBTScalerNode("Saddle", NBT_Type.TAG_BYTE), + }); + + public static readonly NBTCompoundNode SheepSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Sheep"), + new NBTScalerNode("Sheared", NBT_Type.TAG_BYTE), + new NBTScalerNode("Color", NBT_Type.TAG_BYTE), + }); + + public static readonly NBTCompoundNode CowSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Cow"), + }); + + public static readonly NBTCompoundNode ChickenSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Chicken"), + }); + + public static readonly NBTCompoundNode Slimechema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Slime"), + new NBTScalerNode("Size", NBT_Type.TAG_INT), + }); + + public static readonly NBTCompoundNode WolfSchema = MobSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Wolf"), + new NBTScalerNode("Owner", NBT_Type.TAG_STRING), + new NBTScalerNode("Sitting", NBT_Type.TAG_BYTE), + new NBTScalerNode("Angry", NBT_Type.TAG_BYTE), + }); + + #endregion + } + + public class MobEntity : Entity + { + + } +} diff --git a/Substrate/SubstrateCS/Source/Item.cs b/Substrate/SubstrateCS/Source/Item.cs new file mode 100644 index 0000000..4150b45 --- /dev/null +++ b/Substrate/SubstrateCS/Source/Item.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + using NBT; + using Utility; + + public interface IItemContainer + { + ItemCollection Items { get; } + } + + public class Item : INBTObject, ICopyable + { + public static readonly NBTCompoundNode ItemSchema = new NBTCompoundNode("") + { + new NBTScalerNode("id", NBT_Type.TAG_SHORT), + new NBTScalerNode("Damage", NBT_Type.TAG_SHORT), + new NBTScalerNode("Count", NBT_Type.TAG_BYTE), + }; + + private short _id; + private byte _count; + private short _damage; + + public int ID + { + get { return _id; } + set { _id = (short)value; } + } + + public int Damage + { + get { return _damage; } + set { _damage = (short)value; } + } + + public int Count + { + get { return _count; } + set { _count = (byte)value; } + } + + public Item () + { + } + + #region ICopyable Members + + public Item Copy () + { + Item item = new Item(); + item._id = _id; + item._count = _count; + item._damage = _damage; + + return item; + } + + #endregion + + #region INBTObject Members + + public Item LoadTree (NBT_Value tree) + { + NBT_Compound ctree = tree as NBT_Compound; + if (ctree == null) { + return null; + } + + _id = ctree["id"].ToNBTShort(); + _count = ctree["Count"].ToNBTByte(); + _damage = ctree["Damage"].ToNBTShort(); + + return this; + } + + public Item LoadTreeSafe (NBT_Value tree) + { + if (!ValidateTree(tree)) { + return null; + } + + return LoadTree(tree); + } + + public NBT_Value BuildTree () + { + NBT_Compound tree = new NBT_Compound(); + tree["id"] = new NBT_Short(_id); + tree["Count"] = new NBT_Byte(_count); + tree["Damage"] = new NBT_Short(_damage); + + return tree; + } + + public bool ValidateTree (NBT_Value tree) + { + return new NBTVerifier(tree, ItemSchema).Verify(); + } + + #endregion + } + + public class ItemCollection : INBTObject, ICopyable + { + public static readonly NBTCompoundNode InventorySchema = Item.ItemSchema.MergeInto(new NBTCompoundNode("") + { + new NBTScalerNode("Slot", NBT_Type.TAG_BYTE), + }); + + public static readonly NBTListNode ListSchema = new NBTListNode("", NBT_Type.TAG_COMPOUND, InventorySchema); + + protected Dictionary _items; + protected int _capacity; + + public ItemCollection (int capacity) + { + _capacity = capacity; + _items = new Dictionary(); + } + + public int Capacity + { + get { return _capacity; } + } + + public int Count + { + get { return _items.Count; } + } + + public Item this [int slot] + { + get + { + Item item; + _items.TryGetValue(slot, out item); + return item; + } + + set + { + if (slot < 0 || slot >= _capacity) { + return; + } + _items[slot] = value; + } + } + + public bool ItemExists (int slot) + { + return _items.ContainsKey(slot); + } + + public bool Clear (int slot) + { + return _items.Remove(slot); + } + + public void ClearAllItems () + { + _items.Clear(); + } + + #region ICopyable Members + + public ItemCollection Copy () + { + ItemCollection ic = new ItemCollection(_capacity); + foreach (KeyValuePair item in _items) { + ic[item.Key] = item.Value.Copy(); + } + return ic; + } + + #endregion + + #region INBTObject Members + + public ItemCollection LoadTree (NBT_Value tree) + { + NBT_List ltree = tree as NBT_List; + if (ltree == null) { + return null; + } + + foreach (NBT_Compound item in ltree) { + int slot = item["Slot"].ToNBTByte(); + _items[slot] = new Item().LoadTree(item); + } + + return this; + } + + public ItemCollection LoadTreeSafe (NBT_Value tree) + { + if (!ValidateTree(tree)) { + return null; + } + + return LoadTree(tree); + } + + public NBT_Value BuildTree () + { + NBT_List list = new NBT_List(NBT_Type.TAG_COMPOUND); + + foreach (KeyValuePair item in _items) { + NBT_Compound itemtree = item.Value.BuildTree() as NBT_Compound; + itemtree["Slot"] = new NBT_Byte((byte)item.Key); + list.Add(itemtree); + } + + return list; + } + + public bool ValidateTree (NBT_Value tree) + { + return new NBTVerifier(tree, ListSchema).Verify(); + } + + #endregion + } +} diff --git a/Substrate/SubstrateCS/Source/NBT/JSONSerializer.cs b/Substrate/SubstrateCS/Source/NBT/JSONSerializer.cs new file mode 100644 index 0000000..27ba5dc --- /dev/null +++ b/Substrate/SubstrateCS/Source/NBT/JSONSerializer.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map.NBT +{ + class JSONSerializer + { + public static string Serialize (NBT_Value tag) + { + return Serialize(tag, 0); + } + + public static string Serialize (NBT_Value tag, int level) + { + StringBuilder str = new StringBuilder(); + + if (tag.GetNBTType() == NBT_Type.TAG_COMPOUND) { + SerializeCompound(tag as NBT_Compound, str, level); + } + else if (tag.GetNBTType() == NBT_Type.TAG_LIST) { + SerializeList(tag as NBT_List, str, level); + } + else { + SerializeScaler(tag, str); + } + + return str.ToString(); + } + + private static void SerializeCompound (NBT_Compound tag, StringBuilder str, int level) + { + if (tag.Count == 0) { + str.Append("{ }"); + return; + } + + str.AppendLine(); + AddLine(str, "{", level); + + IEnumerator> en = tag.GetEnumerator(); + bool first = true; + while (en.MoveNext()) { + if (!first) { + str.Append(","); + str.AppendLine(); + } + + KeyValuePair item = en.Current; + Add(str, "\"" + item.Key + "\": ", level + 1); + + if (item.Value.GetNBTType() == NBT_Type.TAG_COMPOUND) { + SerializeCompound(item.Value as NBT_Compound, str, level + 1); + } + else if (item.Value.GetNBTType() == NBT_Type.TAG_LIST) { + SerializeList(item.Value as NBT_List, str, level + 1); + } + else { + SerializeScaler(item.Value, str); + } + + first = false; + } + + str.AppendLine(); + Add(str, "}", level); + } + + private static void SerializeList (NBT_List tag, StringBuilder str, int level) + { + if (tag.Count == 0) { + str.Append("[ ]"); + return; + } + + str.AppendLine(); + AddLine(str, "[", level); + + IEnumerator en = tag.GetEnumerator(); + bool first = true; + while (en.MoveNext()) { + if (!first) { + str.Append(","); + } + + NBT_Value item = en.Current; + + if (item.GetNBTType() == NBT_Type.TAG_COMPOUND) { + SerializeCompound(item as NBT_Compound, str, level + 1); + } + else if (item.GetNBTType() == NBT_Type.TAG_LIST) { + SerializeList(item as NBT_List, str, level + 1); + } + else { + if (!first) { + str.AppendLine(); + } + Indent(str, level + 1); + SerializeScaler(item, str); + } + + + first = false; + } + + str.AppendLine(); + Add(str, "]", level); + } + + private static void SerializeScaler (NBT_Value tag, StringBuilder str) + { + NBT_Type type = tag.GetNBTType(); + switch (tag.GetNBTType()) { + case NBT_Type.TAG_STRING: + str.Append("\"" + tag.ToNBTString().Data + "\""); + break; + + case NBT_Type.TAG_BYTE: + str.Append(tag.ToNBTByte().Data); + break; + + case NBT_Type.TAG_SHORT: + str.Append(tag.ToNBTShort().Data); + break; + + case NBT_Type.TAG_INT: + str.Append(tag.ToNBTInt().Data); + break; + + case NBT_Type.TAG_LONG: + str.Append(tag.ToNBTLong().Data); + break; + + case NBT_Type.TAG_FLOAT: + str.Append(tag.ToNBTFloat().Data); + break; + + case NBT_Type.TAG_DOUBLE: + str.Append(tag.ToNBTDouble().Data); + break; + + case NBT_Type.TAG_BYTE_ARRAY: + str.Append(Convert.ToBase64String(tag.ToNBTByteArray().Data)); + break; + } + } + + private static void AddLine (StringBuilder str, string line, int level) + { + Indent(str, level); + str.AppendLine(line); + } + + private static void Add (StringBuilder str, string line, int level) + { + Indent(str, level); + str.Append(line); + } + + private static void Indent (StringBuilder str, int count) + { + for (int i = 0; i < count; i++) { + str.Append("\t"); + } + } + } +} diff --git a/Substrate/SubstrateCS/Source/NBT/NBT.cs b/Substrate/SubstrateCS/Source/NBT/NBT.cs new file mode 100644 index 0000000..04e4810 --- /dev/null +++ b/Substrate/SubstrateCS/Source/NBT/NBT.cs @@ -0,0 +1,510 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.IO.Compression; + +namespace NBToolkit.Map.NBT +{ + using Map.Utility; + + public interface INBTObject + { + T LoadTree (NBT_Value tree); + T LoadTreeSafe (NBT_Value tree); + + NBT_Value BuildTree (); + + bool ValidateTree (NBT_Value tree); + } + + public class NBT_Tree : ICopyable + { + private Stream _stream = null; + private NBT_Compound _root = null; + + private static NBT_Null _nulltag = new NBT_Null(); + + public NBT_Compound Root + { + get { return _root; } + } + + public NBT_Tree () + { + _root = new NBT_Compound(); + } + + public NBT_Tree (NBT_Compound tree) + { + _root = tree; + } + + public NBT_Tree (Stream s) + { + ReadFrom(s); + } + + public void ReadFrom (Stream s) + { + if (s != null) { + _stream = s; + _root = ReadRoot(); + _stream = null; + } + } + + public void WriteTo (Stream s) + { + if (s != null) { + _stream = s; + + if (_root != null) { + WriteTag("", _root); + } + + _stream = null; + } + } + + private NBT_Value ReadValue (NBT_Type type) + { + switch (type) { + case NBT_Type.TAG_END: + return null; + + case NBT_Type.TAG_BYTE: + return ReadByte(); + + case NBT_Type.TAG_SHORT: + return ReadShort(); + + case NBT_Type.TAG_INT: + return ReadInt(); + + case NBT_Type.TAG_LONG: + return ReadLong(); + + case NBT_Type.TAG_FLOAT: + return ReadFloat(); + + case NBT_Type.TAG_DOUBLE: + return ReadDouble(); + + case NBT_Type.TAG_BYTE_ARRAY: + return ReadByteArray(); + + case NBT_Type.TAG_STRING: + return ReadString(); + + case NBT_Type.TAG_LIST: + return ReadList(); + + case NBT_Type.TAG_COMPOUND: + return ReadCompound(); + } + + throw new Exception(); + } + + private NBT_Value ReadByte () + { + int gzByte = _stream.ReadByte(); + if (gzByte == -1) { + throw new NBTException(NBTException.MSG_GZIP_ENDOFSTREAM); + } + + NBT_Byte val = new NBT_Byte((byte)gzByte); + + return val; + } + + private NBT_Value ReadShort () + { + byte[] gzBytes = new byte[2]; + _stream.Read(gzBytes, 0, 2); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(gzBytes); + } + + NBT_Short val = new NBT_Short(BitConverter.ToInt16(gzBytes, 0)); + + return val; + } + + private NBT_Value ReadInt () + { + byte[] gzBytes = new byte[4]; + _stream.Read(gzBytes, 0, 4); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(gzBytes); + } + + NBT_Int val = new NBT_Int(BitConverter.ToInt32(gzBytes, 0)); + + return val; + } + + private NBT_Value ReadLong () + { + byte[] gzBytes = new byte[8]; + _stream.Read(gzBytes, 0, 8); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(gzBytes); + } + + NBT_Long val = new NBT_Long(BitConverter.ToInt64(gzBytes, 0)); + + return val; + } + + private NBT_Value ReadFloat () + { + byte[] gzBytes = new byte[4]; + _stream.Read(gzBytes, 0, 4); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(gzBytes); + } + + NBT_Float val = new NBT_Float(BitConverter.ToSingle(gzBytes, 0)); + + return val; + } + + private NBT_Value ReadDouble () + { + byte[] gzBytes = new byte[8]; + _stream.Read(gzBytes, 0, 8); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(gzBytes); + } + + NBT_Double val = new NBT_Double(BitConverter.ToDouble(gzBytes, 0)); + + return val; + } + + private NBT_Value ReadByteArray () + { + byte[] lenBytes = new byte[4]; + _stream.Read(lenBytes, 0, 4); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(lenBytes); + } + + int length = BitConverter.ToInt32(lenBytes, 0); + if (length < 0) { + throw new NBTException(NBTException.MSG_READ_NEG); + } + + byte[] data = new byte[length]; + _stream.Read(data, 0, length); + + NBT_ByteArray val = new NBT_ByteArray(data); + + return val; + } + + private NBT_Value ReadString () + { + byte[] lenBytes = new byte[2]; + _stream.Read(lenBytes, 0, 2); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(lenBytes); + } + + short len = BitConverter.ToInt16(lenBytes, 0); + if (len < 0) { + throw new NBTException(NBTException.MSG_READ_NEG); + } + + byte[] strBytes = new byte[len]; + _stream.Read(strBytes, 0, len); + + System.Text.Encoding str = Encoding.GetEncoding(28591); + + NBT_String val = new NBT_String(str.GetString(strBytes)); + + return val; + } + + private NBT_Value ReadList () + { + int gzByte = _stream.ReadByte(); + if (gzByte == -1) { + throw new NBTException(NBTException.MSG_GZIP_ENDOFSTREAM); + } + + NBT_List val = new NBT_List((NBT_Type)gzByte); + if (val.ValueType > (NBT_Type)Enum.GetValues(typeof(NBT_Type)).GetUpperBound(0)) { + throw new NBTException(NBTException.MSG_READ_TYPE); + } + + byte[] lenBytes = new byte[4]; + _stream.Read(lenBytes, 0, 4); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(lenBytes); + } + + int length = BitConverter.ToInt32(lenBytes, 0); + if (length < 0) { + throw new NBTException(NBTException.MSG_READ_NEG); + } + + for (int i = 0; i < length; i++) { + val.Add(ReadValue(val.ValueType)); + } + + return val; + } + + private NBT_Value ReadCompound () + { + NBT_Compound val = new NBT_Compound(); + + while (ReadTag(val)) ; + + return val; + } + + private NBT_Compound ReadRoot () + { + NBT_Type type = (NBT_Type)_stream.ReadByte(); + if (type == NBT_Type.TAG_COMPOUND) { + string name = ReadString().ToNBTString().Data; + return ReadValue(type) as NBT_Compound; + } + + return null; + } + + private bool ReadTag (NBT_Compound parent) + { + //NBT_Tag tag = new NBT_Tag(); + + NBT_Type type = (NBT_Type)_stream.ReadByte(); + if (type != NBT_Type.TAG_END) { + string name = ReadString().ToNBTString().Data; + parent[name] = ReadValue(type); + return true; + } + + return false; + + //tag.Value = ReadValue(type); + + //return tag; + } + + private void WriteValue (NBT_Value val) + { + switch (val.GetNBTType()) { + case NBT_Type.TAG_END: + break; + + case NBT_Type.TAG_BYTE: + WriteByte(val.ToNBTByte()); + break; + + case NBT_Type.TAG_SHORT: + WriteShort(val.ToNBTShort()); + break; + + case NBT_Type.TAG_INT: + WriteInt(val.ToNBTInt()); + break; + + case NBT_Type.TAG_LONG: + WriteLong(val.ToNBTLong()); + break; + + case NBT_Type.TAG_FLOAT: + WriteFloat(val.ToNBTFloat()); + break; + + case NBT_Type.TAG_DOUBLE: + WriteDouble(val.ToNBTDouble()); + break; + + case NBT_Type.TAG_BYTE_ARRAY: + WriteByteArray(val.ToNBTByteArray()); + break; + + case NBT_Type.TAG_STRING: + WriteString(val.ToNBTString()); + break; + + case NBT_Type.TAG_LIST: + WriteList(val.ToNBTList()); + break; + + case NBT_Type.TAG_COMPOUND: + WriteCompound(val.ToNBTCompound()); + break; + } + } + + private void WriteByte (NBT_Byte val) + { + _stream.WriteByte(val.Data); + } + + private void WriteShort (NBT_Short val) + { + byte[] gzBytes = BitConverter.GetBytes(val.Data); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(gzBytes); + } + + _stream.Write(gzBytes, 0, 2); + } + + private void WriteInt (NBT_Int val) + { + byte[] gzBytes = BitConverter.GetBytes(val.Data); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(gzBytes); + } + + _stream.Write(gzBytes, 0, 4); + } + + private void WriteLong (NBT_Long val) + { + byte[] gzBytes = BitConverter.GetBytes(val.Data); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(gzBytes); + } + + _stream.Write(gzBytes, 0, 8); + } + + private void WriteFloat (NBT_Float val) + { + byte[] gzBytes = BitConverter.GetBytes(val.Data); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(gzBytes); + } + + _stream.Write(gzBytes, 0, 4); + } + + private void WriteDouble (NBT_Double val) + { + byte[] gzBytes = BitConverter.GetBytes(val.Data); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(gzBytes); + } + + _stream.Write(gzBytes, 0, 8); + } + + private void WriteByteArray (NBT_ByteArray val) + { + byte[] lenBytes = BitConverter.GetBytes(val.Length); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(lenBytes); + } + + _stream.Write(lenBytes, 0, 4); + _stream.Write(val.Data, 0, val.Length); + } + + private void WriteString (NBT_String val) + { + byte[] lenBytes = BitConverter.GetBytes((short)val.Length); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(lenBytes); + } + + _stream.Write(lenBytes, 0, 2); + + System.Text.Encoding str = Encoding.GetEncoding(28591); + byte[] gzBytes = str.GetBytes(val.Data); + + _stream.Write(gzBytes, 0, gzBytes.Length); + } + + private void WriteList (NBT_List val) + { + byte[] lenBytes = BitConverter.GetBytes(val.Count); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(lenBytes); + } + + _stream.WriteByte((byte)val.ValueType); + _stream.Write(lenBytes, 0, 4); + + foreach (NBT_Value v in val) { + WriteValue(v); + } + } + + private void WriteCompound (NBT_Compound val) + { + foreach (KeyValuePair item in val) { + WriteTag(item.Key, item.Value); + } + + WriteTag(null, _nulltag); + } + + private void WriteTag (string name, NBT_Value val) + { + _stream.WriteByte((byte)val.GetNBTType()); + + if (val.GetNBTType() != NBT_Type.TAG_END) { + WriteString(name); + WriteValue(val); + } + } + + #region ICopyable Members + + public NBT_Tree Copy () + { + NBT_Tree tree = new NBT_Tree(); + tree._root = _root.Copy() as NBT_Compound; + + return tree; + } + + #endregion + } + + public class NBTException : Exception + { + public const String MSG_GZIP_ENDOFSTREAM = "Gzip Error: Unexpected end of stream"; + + public const String MSG_READ_NEG = "Read Error: Negative length"; + public const String MSG_READ_TYPE = "Read Error: Invalid value type"; + + public NBTException () { } + + public NBTException (String msg) : base(msg) { } + + public NBTException (String msg, Exception innerException) : base(msg, innerException) { } + } + + public class InvalidNBTObjectException : Exception { } + + public class InvalidTagException : Exception { } + + public class InvalidValueException : Exception { } +} diff --git a/Substrate/SubstrateCS/Source/NBT/NBTSchema.cs b/Substrate/SubstrateCS/Source/NBT/NBTSchema.cs new file mode 100644 index 0000000..c4de4ee --- /dev/null +++ b/Substrate/SubstrateCS/Source/NBT/NBTSchema.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map.NBT +{ + public abstract class NBTSchemaNode + { + private string _name; + + public string Name + { + get { return _name; } + } + + public NBTSchemaNode (string name) + { + _name = name; + } + + public virtual NBT_Value BuildDefaultTree () + { + return null; + } + } + + public class NBTScalerNode : NBTSchemaNode + { + private NBT_Type _type; + + public NBT_Type Type + { + get { return _type; } + } + + public NBTScalerNode (string name, NBT_Type type) + : base(name) + { + _type = type; + } + + public override NBT_Value BuildDefaultTree () + { + switch (_type) { + case NBT_Type.TAG_STRING: + return new NBT_String(); + + case NBT_Type.TAG_BYTE: + return new NBT_Byte(); + + case NBT_Type.TAG_SHORT: + return new NBT_Short(); + + case NBT_Type.TAG_INT: + return new NBT_Int(); + + case NBT_Type.TAG_LONG: + return new NBT_Long(); + + case NBT_Type.TAG_FLOAT: + return new NBT_Float(); + + case NBT_Type.TAG_DOUBLE: + return new NBT_Double(); + } + + return null; + } + } + + public class NBTStringNode : NBTSchemaNode + { + private string _value = ""; + private int _length; + + public int Length + { + get { return _length; } + } + + public string Value + { + get { return _value; } + } + + public NBTStringNode (string name, string value) + : base(name) + { + _value = value; + } + + public NBTStringNode (string name, int length) + : base(name) + { + _length = length; + } + + public override NBT_Value BuildDefaultTree () + { + if (_value.Length > 0) { + return new NBT_String(_value); + } + + return new NBT_String(); + } + } + + public class NBTArrayNode : NBTSchemaNode + { + private int _length; + + public int Length + { + get { return _length; } + } + + public NBTArrayNode (string name) + : base(name) + { + _length = 0; + } + + public NBTArrayNode (string name, int length) + : base(name) + { + _length = length; + } + + public override NBT_Value BuildDefaultTree () + { + return new NBT_ByteArray(new byte[_length]); + } + } + + public class NBTListNode : NBTSchemaNode + { + private NBT_Type _type; + private int _length; + private NBTSchemaNode _subschema; + + public int Length + { + get { return _length; } + } + + public NBT_Type Type + { + get { return _type; } + } + + public NBTSchemaNode SubSchema + { + get { return _subschema; } + } + + public NBTListNode (string name, NBT_Type type) + : base(name) + { + _type = type; + } + + public NBTListNode (string name, NBT_Type type, int length) + : base(name) + { + _type = type; + _length = length; + } + + public NBTListNode (string name, NBT_Type type, NBTSchemaNode subschema) + : base(name) + { + _type = type; + _subschema = subschema; + } + + public NBTListNode (string name, NBT_Type type, int length, NBTSchemaNode subschema) + : base(name) + { + _type = type; + _length = length; + _subschema = subschema; + } + + public override NBT_Value BuildDefaultTree () + { + if (_length == 0) { + return new NBT_List(_type); + } + + NBT_List list = new NBT_List(_type); + for (int i = 0; i < _length; i++) { + list.Add(_subschema.BuildDefaultTree()); + } + + return list; + } + } + + public class NBTCompoundNode : NBTSchemaNode, ICollection + { + private List _subnodes; + + #region ICollection Members + + public void Add (NBTSchemaNode item) + { + _subnodes.Add(item); + } + + public void Clear () + { + _subnodes.Clear(); + } + + public bool Contains (NBTSchemaNode item) + { + return _subnodes.Contains(item); + } + + public void CopyTo (NBTSchemaNode[] array, int arrayIndex) + { + _subnodes.CopyTo(array, arrayIndex); + } + + public int Count + { + get { return _subnodes.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public bool Remove (NBTSchemaNode item) + { + return _subnodes.Remove(item); + } + + #endregion + + #region IEnumerable Members + + public IEnumerator GetEnumerator () + { + return _subnodes.GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () + { + return _subnodes.GetEnumerator(); + } + + #endregion + + public NBTCompoundNode () + : base("") + { + _subnodes = new List(); + } + + public NBTCompoundNode (string name) + : base(name) + { + _subnodes = new List(); + } + + public NBTCompoundNode MergeInto (NBTCompoundNode tree) + { + foreach (NBTSchemaNode node in _subnodes) { + NBTSchemaNode f = tree._subnodes.Find(n => n.Name == node.Name); + if (f != null) { + continue; + } + tree.Add(node); + } + + return tree; + } + + public override NBT_Value BuildDefaultTree () + { + NBT_Compound list = new NBT_Compound(); + foreach (NBTSchemaNode node in _subnodes) { + list[node.Name] = node.BuildDefaultTree(); + } + + return list; + } + } +} diff --git a/Substrate/SubstrateCS/Source/NBT/NBTValues.cs b/Substrate/SubstrateCS/Source/NBT/NBTValues.cs new file mode 100644 index 0000000..e5e54e7 --- /dev/null +++ b/Substrate/SubstrateCS/Source/NBT/NBTValues.cs @@ -0,0 +1,630 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map.NBT { + + using Map.Utility; + + /// + /// Describes the type of value held by an NBT_Tag + /// + public enum NBT_Type + { + TAG_END = 0, + TAG_BYTE = 1, // 8 bits signed + TAG_SHORT = 2, // 16 bits signed + TAG_INT = 3, // 32 bits signed + TAG_LONG = 4, // 64 bits signed + TAG_FLOAT = 5, + TAG_DOUBLE = 6, + TAG_BYTE_ARRAY = 7, + TAG_STRING = 8, + TAG_LIST = 9, + TAG_COMPOUND = 10 + } + + public abstract class NBT_Value : ICopyable + { + virtual public NBT_Null ToNBTNull () { throw new InvalidCastException(); } + virtual public NBT_Byte ToNBTByte () { throw new InvalidCastException(); } + virtual public NBT_Short ToNBTShort () { throw new InvalidCastException(); } + virtual public NBT_Int ToNBTInt () { throw new InvalidCastException(); } + virtual public NBT_Long ToNBTLong () { throw new InvalidCastException(); } + virtual public NBT_Float ToNBTFloat () { throw new InvalidCastException(); } + virtual public NBT_Double ToNBTDouble () { throw new InvalidCastException(); } + virtual public NBT_ByteArray ToNBTByteArray () { throw new InvalidCastException(); } + virtual public NBT_String ToNBTString () { throw new InvalidCastException(); } + virtual public NBT_List ToNBTList () { throw new InvalidCastException(); } + virtual public NBT_Compound ToNBTCompound () { throw new InvalidCastException(); } + + virtual public NBT_Type GetNBTType () { return NBT_Type.TAG_END; } + + public virtual NBT_Value Copy () + { + return null; + } + } + + public class NBT_Null : NBT_Value + { + override public NBT_Null ToNBTNull () { return this; } + override public NBT_Type GetNBTType () { return NBT_Type.TAG_END; } + + public override NBT_Value Copy () + { + return new NBT_Null(); + } + } + + public class NBT_Byte : NBT_Value + { + private byte _data = 0; + + override public NBT_Byte ToNBTByte () { return this; } + override public NBT_Type GetNBTType () { return NBT_Type.TAG_BYTE; } + + public byte Data + { + get { return _data; } + set { _data = value; } + } + + public NBT_Byte () { } + + public NBT_Byte (byte d) + { + _data = d; + } + + public override NBT_Value Copy () + { + return new NBT_Byte(_data); + } + + public static implicit operator NBT_Byte (byte b) + { + return new NBT_Byte(b); + } + + public static implicit operator byte (NBT_Byte b) + { + return b._data; + } + } + + public class NBT_Short : NBT_Value + { + private short _data = 0; + + override public NBT_Short ToNBTShort () { return this; } + override public NBT_Type GetNBTType () { return NBT_Type.TAG_SHORT; } + + public short Data + { + get { return _data; } + set { _data = value; } + } + + public NBT_Short () { } + + public NBT_Short (short d) + { + _data = d; + } + + public override NBT_Value Copy () + { + return new NBT_Short(_data); + } + + public static implicit operator NBT_Short (short s) + { + return new NBT_Short(s); + } + + public static implicit operator short (NBT_Short s) + { + return s._data; + } + } + + public class NBT_Int : NBT_Value + { + private int _data = 0; + + override public NBT_Int ToNBTInt () { return this; } + override public NBT_Type GetNBTType () { return NBT_Type.TAG_INT; } + + public int Data + { + get { return _data; } + set { _data = value; } + } + + public NBT_Int () { } + + public NBT_Int (int d) + { + _data = d; + } + + public override NBT_Value Copy () + { + return new NBT_Int(_data); + } + + public static implicit operator NBT_Int (int i) + { + return new NBT_Int(i); + } + + public static implicit operator int (NBT_Int i) + { + return i._data; + } + } + + public class NBT_Long : NBT_Value + { + private long _data = 0; + + override public NBT_Long ToNBTLong () { return this; } + override public NBT_Type GetNBTType () { return NBT_Type.TAG_LONG; } + + public long Data + { + get { return _data; } + set { _data = value; } + } + + public NBT_Long () { } + + public NBT_Long (long d) + { + _data = d; + } + + public override NBT_Value Copy () + { + return new NBT_Long(_data); + } + + public static implicit operator NBT_Long (long l) + { + return new NBT_Long(l); + } + + public static implicit operator long (NBT_Long l) + { + return l._data; + } + } + + public class NBT_Float : NBT_Value + { + private float _data = 0; + + override public NBT_Float ToNBTFloat () { return this; } + override public NBT_Type GetNBTType () { return NBT_Type.TAG_FLOAT; } + + public float Data + { + get { return _data; } + set { _data = value; } + } + + public NBT_Float () { } + + public NBT_Float (float d) + { + _data = d; + } + + public override NBT_Value Copy () + { + return new NBT_Float(_data); + } + + public static implicit operator NBT_Float (float f) + { + return new NBT_Float(f); + } + + public static implicit operator float (NBT_Float f) + { + return f._data; + } + } + + public class NBT_Double : NBT_Value + { + private double _data = 0; + + override public NBT_Double ToNBTDouble () { return this; } + override public NBT_Type GetNBTType () { return NBT_Type.TAG_DOUBLE; } + + public double Data + { + get { return _data; } + set { _data = value; } + } + + public NBT_Double () { } + + public NBT_Double (double d) + { + _data = d; + } + + public override NBT_Value Copy () + { + return new NBT_Double(_data); + } + + public static implicit operator NBT_Double (double d) + { + return new NBT_Double(d); + } + + public static implicit operator double (NBT_Double d) + { + return d._data; + } + } + + public class NBT_ByteArray : NBT_Value + { + private byte[] _data = null; + + override public NBT_ByteArray ToNBTByteArray () { return this; } + override public NBT_Type GetNBTType () { return NBT_Type.TAG_BYTE_ARRAY; } + + public byte[] Data + { + get { return _data; } + set { _data = value; } + } + + public int Length + { + get { return _data.Length; } + } + + public NBT_ByteArray () { } + + public NBT_ByteArray (byte[] d) + { + _data = d; + } + + public override NBT_Value Copy () + { + byte[] arr = new byte[_data.Length]; + _data.CopyTo(arr, 0); + + return new NBT_ByteArray(arr); + } + + public byte this [int index] { + get { return _data[index]; } + set { _data[index] = value; } + } + + public static implicit operator NBT_ByteArray (byte[] b) + { + return new NBT_ByteArray(b); + } + } + + public class NBT_String : NBT_Value + { + private string _data = ""; + + override public NBT_String ToNBTString () { return this; } + override public NBT_Type GetNBTType () { return NBT_Type.TAG_STRING; } + + public string Data + { + get { return _data; } + set { _data = value; } + } + + public int Length + { + get { return _data.Length; } + } + + public NBT_String () { } + + public NBT_String (string d) + { + _data = d; + } + + public override NBT_Value Copy () + { + return new NBT_String(_data); + } + + public static implicit operator NBT_String (string s) + { + return new NBT_String(s); + } + + public static implicit operator string (NBT_String s) + { + return s._data; + } + } + + public class NBT_List : NBT_Value, IList + { + private NBT_Type _type = NBT_Type.TAG_END; + + private List _items = null; + + override public NBT_List ToNBTList () { return this; } + override public NBT_Type GetNBTType () { return NBT_Type.TAG_LIST; } + + public int Count + { + get { return _items.Count; } + } + + public NBT_Type ValueType + { + get { return _type; } + } + + public NBT_List (NBT_Type type) + { + _type = type; + _items = new List(); + } + + public NBT_List (NBT_Type type, List items) + { + _type = type; + _items = items; + } + + public override NBT_Value Copy () + { + NBT_List list = new NBT_List(_type); + foreach (NBT_Value item in _items) { + list.Add(item.Copy()); + } + return list; + } + + #region IList Members + + public int IndexOf (NBT_Value item) + { + return _items.IndexOf(item); + } + + public void Insert (int index, NBT_Value item) + { + _items.Insert(index, item); + } + + public void RemoveAt (int index) + { + _items.RemoveAt(index); + } + + public NBT_Value this[int index] + { + get + { + return _items[index]; + } + set + { + _items[index] = value; + } + } + + #endregion + + #region ICollection Members + + public void Add (NBT_Value item) + { + _items.Add(item); + } + + public void Clear () + { + _items.Clear(); + } + + public bool Contains (NBT_Value item) + { + return _items.Contains(item); + } + + public void CopyTo (NBT_Value[] array, int arrayIndex) + { + _items.CopyTo(array, arrayIndex); + } + + public bool IsReadOnly + { + get { return false; } + } + + public bool Remove (NBT_Value item) + { + return _items.Remove(item); + } + + #endregion + + #region IEnumerable Members + + public IEnumerator GetEnumerator () + { + return _items.GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () + { + return _items.GetEnumerator(); + } + + #endregion + } + + public class NBT_Compound : NBT_Value, IDictionary + { + private Dictionary _tags; + + override public NBT_Compound ToNBTCompound () { return this; } + override public NBT_Type GetNBTType () { return NBT_Type.TAG_COMPOUND; } + + public int Count + { + get { return _tags.Count; } + } + + public NBT_Compound () + { + _tags = new Dictionary(); + } + + public override NBT_Value Copy () + { + NBT_Compound list = new NBT_Compound(); + foreach (KeyValuePair item in _tags) { + list[item.Key] = item.Value.Copy(); + } + return list; + } + + #region IDictionary Members + + public void Add (string key, NBT_Value value) + { + _tags.Add(key, value); + } + + public bool ContainsKey (string key) + { + return _tags.ContainsKey(key); + } + + public ICollection Keys + { + get { return _tags.Keys; } + } + + public bool Remove (string key) + { + return _tags.Remove(key); + } + + public bool TryGetValue (string key, out NBT_Value value) + { + return _tags.TryGetValue(key, out value); + } + + public ICollection Values + { + get { return _tags.Values; } + } + + public NBT_Value this[string key] + { + get + { + return _tags[key]; + } + set + { + _tags[key] = value; + } + } + + #endregion + + #region ICollection> Members + + public void Add (KeyValuePair item) + { + _tags.Add(item.Key, item.Value); + } + + public void Clear () + { + _tags.Clear(); + } + + public bool Contains (KeyValuePair item) + { + NBT_Value value; + if (!_tags.TryGetValue(item.Key, out value)) { + return false; + } + return value == item.Value; + } + + public void CopyTo (KeyValuePair[] array, int arrayIndex) + { + if (array == null) { + throw new ArgumentNullException(); + } + if (arrayIndex < 0) { + throw new ArgumentOutOfRangeException(); + } + if (array.Length - arrayIndex < _tags.Count) { + throw new ArgumentException(); + } + + foreach (KeyValuePair item in _tags) { + array[arrayIndex] = item; + arrayIndex++; + } + } + + public bool IsReadOnly + { + get { return false; } + } + + public bool Remove (KeyValuePair item) + { + if (Contains(item)) { + _tags.Remove(item.Key); + return true; + } + return false; + } + + #endregion + + #region IEnumerable> Members + + public IEnumerator> GetEnumerator () + { + return _tags.GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () + { + return _tags.GetEnumerator(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Substrate/SubstrateCS/Source/NBT/NBTVerifier.cs b/Substrate/SubstrateCS/Source/NBT/NBTVerifier.cs new file mode 100644 index 0000000..1cf7046 --- /dev/null +++ b/Substrate/SubstrateCS/Source/NBT/NBTVerifier.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map.NBT +{ + public delegate void MissingTagHandler (Object o, TagEventArgs e); + public delegate void InvalidTagTypeHandler (Object o, TagEventArgs e); + public delegate void InvalidTagValueHandler (Object o, TagEventArgs e); + + public interface INBTVerifier + { + event MissingTagHandler MissingTag; + event InvalidTagTypeHandler InvalidTagType; + event InvalidTagValueHandler InvalidTagValue; + + bool Verify (); + } + + public class TagEventArgs : EventArgs + { + protected string _tagName; + protected NBT_Value _tag; + protected NBTSchemaNode _schema; + + public string TagName + { + get { return _tagName; } + } + + public TagEventArgs (string tagName) + : base() + { + _tagName = tagName; + } + + public TagEventArgs (string tagName, NBT_Value tag) + : base() + { + _tag = tag; + _tagName = tagName; + } + + public TagEventArgs (NBTSchemaNode schema, NBT_Value tag) + : base() + { + _tag = tag; + _schema = schema; + } + } + + public class NBTVerifier : INBTVerifier + { + private NBT_Value _root; + private NBTSchemaNode _schema; + + public event MissingTagHandler MissingTag; + public event InvalidTagTypeHandler InvalidTagType; + public event InvalidTagValueHandler InvalidTagValue; + + public NBTVerifier () { } + + public NBTVerifier (NBT_Value root, NBTSchemaNode schema) + { + _root = root; + _schema = schema; + } + + public bool Verify () + { + return Verify(_root, _schema); + } + + static NBTCompoundNode inventorySchema = new NBTCompoundNode("") + { + new NBTScalerNode("id", NBT_Type.TAG_SHORT), + new NBTScalerNode("Damage", NBT_Type.TAG_SHORT), + new NBTScalerNode("Count", NBT_Type.TAG_BYTE), + new NBTScalerNode("Slot", NBT_Type.TAG_BYTE), + }; + + private bool Verify (NBT_Value tag, NBTSchemaNode schema) + { + if (tag == null) { + OnMissingTag(new TagEventArgs(schema.Name)); + return false; + } + + NBTScalerNode scaler = schema as NBTScalerNode; + if (scaler != null) { + return VerifyScaler(tag, scaler); + } + + NBTStringNode str = schema as NBTStringNode; + if (str != null) { + return VerifyString(tag, str); + } + + NBTArrayNode array = schema as NBTArrayNode; + if (array != null) { + return VerifyArray(tag, array); + } + + NBTListNode list = schema as NBTListNode; + if (list != null) { + return VerifyList(tag, list); + } + + NBTCompoundNode compound = schema as NBTCompoundNode; + if (compound != null) { + return VerifyCompound(tag, compound); + } + + return false; + } + + private bool VerifyScaler (NBT_Value tag, NBTScalerNode schema) + { + if (tag.GetNBTType() != schema.Type) { + OnInvalidTagType(new TagEventArgs(schema.Name, tag)); + return false; + } + + return true; + } + + private bool VerifyString (NBT_Value tag, NBTStringNode schema) + { + NBT_String stag = tag as NBT_String; + if (stag == null) { + OnInvalidTagType(new TagEventArgs(schema, tag)); + return false; + } + if (schema.Length > 0 && stag.Length > schema.Length) { + OnInvalidTagValue(new TagEventArgs(schema, tag)); + return false; + } + if (schema.Value != null && stag.Data != schema.Value) { + OnInvalidTagValue(new TagEventArgs(schema, tag)); + return false; + } + + return true; + } + + + private bool VerifyArray (NBT_Value tag, NBTArrayNode schema) + { + NBT_ByteArray atag = tag as NBT_ByteArray; + if (atag == null) { + OnInvalidTagType(new TagEventArgs(schema, tag)); + return false; + } + if (schema.Length > 0 && atag.Length != schema.Length) { + OnInvalidTagValue(new TagEventArgs(schema, tag)); + return false; + } + + return true; + } + + private bool VerifyList (NBT_Value tag, NBTListNode schema) + { + NBT_List ltag = tag as NBT_List; + if (ltag == null) { + OnInvalidTagType(new TagEventArgs(schema, tag)); + return false; + } + if (ltag.Count > 0 && ltag.ValueType != schema.Type) { + OnInvalidTagValue(new TagEventArgs(schema, tag)); + return false; + } + if (schema.Length > 0 && ltag.Count != schema.Length) { + OnInvalidTagValue(new TagEventArgs(schema, tag)); + return false; + } + + // Patch up empty lists + //if (schema.Length == 0) { + // tag = new NBT_List(schema.Type); + //} + + bool pass = true; + + // If a subschema is set, test all items in list against it + + if (schema.SubSchema != null) { + foreach (NBT_Value v in ltag) { + pass = Verify(v, schema.SubSchema) && pass; + } + } + + return pass; + } + + private bool VerifyCompound (NBT_Value tag, NBTCompoundNode schema) + { + NBT_Compound ctag = tag as NBT_Compound; + if (ctag == null) { + OnInvalidTagType(new TagEventArgs(schema, tag)); + return false; + } + + bool pass = true; + + foreach (NBTSchemaNode node in schema) { + NBT_Value value; + ctag.TryGetValue(node.Name, out value); + + pass = Verify(value, node) && pass; + } + + return pass; + } + + #region Event Handlers + + protected void OnMissingTag (TagEventArgs e) + { + if (MissingTag != null) { + MissingTag(this, e); + } + } + + protected void OnInvalidTagType (TagEventArgs e) + { + if (InvalidTagType != null) { + InvalidTagType(this, e); + } + } + + protected void OnInvalidTagValue (TagEventArgs e) + { + if (InvalidTagValue != null) { + InvalidTagValue(this, e); + } + } + + #endregion + } +} diff --git a/Substrate/SubstrateCS/Source/Region.cs b/Substrate/SubstrateCS/Source/Region.cs new file mode 100644 index 0000000..2b7dce1 --- /dev/null +++ b/Substrate/SubstrateCS/Source/Region.cs @@ -0,0 +1,334 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.IO; + +namespace NBToolkit.Map +{ + using NBT; + + public class Region : IDisposable, IChunkContainer, IChunkCache + { + protected int _rx; + protected int _rz; + protected bool _disposed = false; + + protected RegionManager _regionMan; + + protected static Regex _namePattern = new Regex("r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mcr$"); + + protected WeakReference _regionFile; + + protected Dictionary _cache; + protected Dictionary _dirty; + + public int X + { + get { return _rx; } + } + + public int Z + { + get { return _rz; } + } + + public Region (RegionManager rm, int rx, int rz) + { + _regionMan = rm; + _regionFile = new WeakReference(null); + _rx = rx; + _rz = rz; + + _cache = new Dictionary(); + _dirty = new Dictionary(); + + if (!File.Exists(GetFilePath())) { + throw new FileNotFoundException(); + } + } + + public Region (RegionManager rm, string filename) + { + _regionMan = rm; + _regionFile = new WeakReference(null); + + ParseFileName(filename, out _rx, out _rz); + + if (!File.Exists(Path.Combine(_regionMan.GetRegionPath(), filename))) { + throw new FileNotFoundException(); + } + } + + ~Region () + { + Dispose(false); + } + + public void Dispose () + { + Dispose(true); + System.GC.SuppressFinalize(this); + } + + protected virtual void Dispose (bool disposing) + { + if (!_disposed) { + if (disposing) { + // Cleanup managed resources + RegionFile rf = _regionFile.Target as RegionFile; + if (rf != null) { + rf.Dispose(); + rf = null; + } + } + + // Cleanup unmanaged resources + } + _disposed = true; + } + + public string GetFileName () + { + return "r." + _rx + "." + _rz + ".mcr"; + + } + + public static bool TestFileName (string filename) + { + Match match = _namePattern.Match(filename); + if (!match.Success) { + return false; + } + + return true; + } + + public static bool ParseFileName (string filename, out int x, out int z) + { + x = 0; + z = 0; + + Match match = _namePattern.Match(filename); + if (!match.Success) { + return false; + } + + x = Convert.ToInt32(match.Groups[1].Value); + z = Convert.ToInt32(match.Groups[2].Value); + return true; + } + + public string GetFilePath () + { + return System.IO.Path.Combine(_regionMan.GetRegionPath(), GetFileName()); + } + + protected RegionFile GetRegionFile () + { + RegionFile rf = _regionFile.Target as RegionFile; + if (rf == null) { + rf = new RegionFile(GetFilePath()); + _regionFile.Target = rf; + } + + return rf; + } + + public NBT_Tree GetChunkTree (int lcx, int lcz) + { + RegionFile rf = GetRegionFile(); + Stream nbtstr = rf.GetChunkDataInputStream(lcx, lcz); + if (nbtstr == null) { + return null; + } + + return new NBT_Tree(nbtstr); + } + + public bool SaveChunkTree (int lcx, int lcz, NBT_Tree tree) + { + RegionFile rf = GetRegionFile(); + Stream zipstr = rf.GetChunkDataOutputStream(lcx, lcz); + if (zipstr == null) { + return false; + } + + tree.WriteTo(zipstr); + zipstr.Close(); + + return true; + } + + public Stream GetChunkOutStream (int lcx, int lcz) + { + RegionFile rf = GetRegionFile(); + return rf.GetChunkDataOutputStream(lcx, lcz); + } + + public int ChunkCount () + { + RegionFile rf = GetRegionFile(); + + int count = 0; + for (int x = 0; x < ChunkManager.REGION_XLEN; x++) { + for (int z = 0; z < ChunkManager.REGION_ZLEN; z++) { + if (rf.HasChunk(x, z)) { + count++; + } + } + } + + return count; + } + + public ChunkRef GetChunkRef (int lcx, int lcz, IChunkCache cache) + { + ChunkKey k = new ChunkKey(lcx, lcz); + + ChunkRef c = null; + + WeakReference chunkref = null; + if (_cache.TryGetValue(k, out chunkref)) { + c = chunkref.Target as ChunkRef; + } + else { + _cache.Add(k, new WeakReference(null)); + } + + if (c != null) { + return c; + } + + try { + c = new ChunkRef(this, cache, lcx, lcz); + _cache[k].Target = c; + return c; + } + catch (MissingChunkException) { + return null; + } + } + + + #region IChunkCollection Members + + public int ChunkGlobalX (int cx) + { + return _rx * ChunkManager.REGION_XLEN + cx; + } + + public int ChunkGlobalZ (int cz) + { + return _rz * ChunkManager.REGION_ZLEN + cz; + } + + public int ChunkLocalX (int cx) + { + return cx; + } + + public int ChunkLocalZ (int cz) + { + return cz; + } + + public Chunk GetChunk (int lcx, int lcz) + { + if (!ChunkExists(lcx, lcz)) { + return null; + } + + return new Chunk(GetChunkTree(lcx, lcz)); + } + + public ChunkRef GetChunkRef (int lcx, int lcz) + { + return GetChunkRef(lcx, lcz, this); + } + + public bool ChunkExists (int lcx, int lcz) + { + RegionFile rf = GetRegionFile(); + return rf.HasChunk(lcx, lcz); + } + + public bool DeleteChunk (int lcx, int lcz) + { + RegionFile rf = GetRegionFile(); + if (!rf.HasChunk(lcx, lcz)) { + return false; + } + + rf.DeleteChunk(lcx, lcz); + + ChunkKey k = new ChunkKey(lcx, lcz); + _cache.Remove(k); + _dirty.Remove(k); + + if (ChunkCount() == 0) { + _regionMan.DeleteRegion(X, Z); + } + + return true; + } + + public int Save () + { + int saved = 0; + foreach (ChunkRef c in _dirty.Values) { + int lcx = c.LocalX; + int lcz = c.LocalZ; + + if (!ChunkExists(lcx, lcz)) { + throw new MissingChunkException(); + } + + if (c.Save(GetChunkOutStream(lcx, lcz))) { + saved++; + } + } + + _dirty.Clear(); + return saved; + } + + public bool SaveChunk (Chunk chunk) + { + return chunk.Save(GetChunkOutStream(ChunkLocalX(chunk.X), ChunkLocalZ(chunk.Z))); + } + + #endregion + + + #region IChunkCache Members + + public bool MarkChunkDirty (ChunkRef chunk) + { + int lcx = chunk.LocalX; + int lcz = chunk.LocalZ; + + ChunkKey k = new ChunkKey(lcx, lcz); + if (!_dirty.ContainsKey(k)) { + _dirty.Add(k, GetChunkRef(lcx, lcz)); + return true; + } + return false; + } + + public bool MarkChunkClean (ChunkRef chunk) + { + int lcx = chunk.LocalX; + int lcz = chunk.LocalZ; + + ChunkKey k = new ChunkKey(lcx, lcz); + if (_dirty.ContainsKey(k)) { + _dirty.Remove(k); + return true; + } + return false; + } + + #endregion + } +} diff --git a/Substrate/SubstrateCS/Source/RegionEnumerator.cs b/Substrate/SubstrateCS/Source/RegionEnumerator.cs new file mode 100644 index 0000000..d87b1c1 --- /dev/null +++ b/Substrate/SubstrateCS/Source/RegionEnumerator.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.IO; + +namespace NBToolkit.Map +{ + public class RegionList : IEnumerable + { + private List _regions; + + public RegionList (List regs) + { + _regions = regs; + } + + public RegionList (RegionManager rm) + { + _regions = new List(); + + if (!Directory.Exists(rm.GetRegionPath())) { + throw new DirectoryNotFoundException(); + } + + string[] files = Directory.GetFiles(rm.GetRegionPath()); + _regions.Capacity = files.Length; + + foreach (string file in files) { + try { + Region r = rm.GetRegion(file); + _regions.Add(r); + } + catch (ArgumentException) { + continue; + } + } + } + + IEnumerator IEnumerable.GetEnumerator () { + return (IEnumerator)GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator () + { + return (IEnumerator)GetEnumerator(); + } + + public RegionEnumerator GetEnumerator () + { + return new RegionEnumerator(_regions); + } + } + + public class RegionEnumerator : IEnumerator + { + protected List _regions; + + protected int _pos = -1; + + public RegionEnumerator (List regs) + { + _regions = regs; + } + + public RegionEnumerator (RegionManager rm) + { + _regions = new List(); + + if (!Directory.Exists(rm.GetRegionPath())) { + throw new DirectoryNotFoundException(); + } + + string[] files = Directory.GetFiles(rm.GetRegionPath()); + _regions.Capacity = files.Length; + + foreach (string file in files) { + try { + Region r = rm.GetRegion(file); + _regions.Add(r); + } + catch (ArgumentException) { + continue; + } + } + } + + public bool MoveNext () + { + _pos++; + return (_pos < _regions.Count); + } + + public void Reset () + { + _pos = -1; + } + + void IDisposable.Dispose () { } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + Region IEnumerator.Current + { + get + { + return Current; + } + } + + public Region Current + { + get + { + try { + return _regions[_pos]; + } + catch (IndexOutOfRangeException) { + throw new InvalidOperationException(); + } + } + } + } +} diff --git a/Substrate/SubstrateCS/Source/RegionFile.cs b/Substrate/SubstrateCS/Source/RegionFile.cs new file mode 100644 index 0000000..49bfb99 --- /dev/null +++ b/Substrate/SubstrateCS/Source/RegionFile.cs @@ -0,0 +1,442 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using Ionic.Zlib; +using System.Collections; + +namespace NBToolkit.Map +{ + public class RegionFile : IDisposable { + + private const int VERSION_GZIP = 1; + private const int VERSION_DEFLATE = 2; + + private const int SECTOR_BYTES = 4096; + private const int SECTOR_INTS = SECTOR_BYTES / 4; + + const int CHUNK_HEADER_SIZE = 5; + + private static byte[] emptySector = new byte[4096]; + + private string fileName; + private FileStream file; + private int[] offsets; + private int[] chunkTimestamps; + private List sectorFree; + private int sizeDelta; + private long lastModified = 0; + + protected bool _disposed = false; + + public RegionFile(string path) { + offsets = new int[SECTOR_INTS]; + chunkTimestamps = new int[SECTOR_INTS]; + + fileName = path; + Debugln("REGION LOAD " + fileName); + + sizeDelta = 0; + + ReadFile(); + } + + ~RegionFile () + { + Dispose(false); + } + + public void Dispose () + { + Dispose(true); + System.GC.SuppressFinalize(this); + } + + protected virtual void Dispose (bool disposing) + { + if (!_disposed) { + if (disposing) { + // Cleanup managed resources + } + + // Cleanup unmanaged resources + if (file != null) { + file.Close(); + file = null; + } + } + _disposed = true; + } + + protected void ReadFile () + { + // Get last udpate time + long newModified = 0; + try { + if (File.Exists(fileName)) { + newModified = Timestamp(File.GetLastWriteTime(fileName)); + } + } + catch (UnauthorizedAccessException e) { + Console.WriteLine(e.Message); + return; + } + + // If it hasn't been modified, we don't need to do anything + if (newModified == lastModified) { + return; + } + + try { + file = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite); + + //using (file) { + if (file.Length < SECTOR_BYTES) { + byte[] int0 = BitConverter.GetBytes((int)0); + + /* we need to write the chunk offset table */ + for (int i = 0; i < SECTOR_INTS; ++i) { + file.Write(int0, 0, 4); + } + // write another sector for the timestamp info + for (int i = 0; i < SECTOR_INTS; ++i) { + file.Write(int0, 0, 4); + } + + sizeDelta += SECTOR_BYTES * 2; + } + + if ((file.Length & 0xfff) != 0) { + /* the file size is not a multiple of 4KB, grow it */ + for (int i = 0; i < (file.Length & 0xfff); ++i) { + file.WriteByte(0); + } + } + + /* set up the available sector map */ + int nSectors = (int)file.Length / SECTOR_BYTES; + sectorFree = new List(nSectors); + + for (int i = 0; i < nSectors; ++i) { + sectorFree.Add(true); + } + + sectorFree[0] = false; // chunk offset table + sectorFree[1] = false; // for the last modified info + + file.Seek(0, SeekOrigin.Begin); + for (int i = 0; i < SECTOR_INTS; ++i) { + byte[] offsetBytes = new byte[4]; + file.Read(offsetBytes, 0, 4); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(offsetBytes); + } + int offset = BitConverter.ToInt32(offsetBytes, 0); + + offsets[i] = offset; + if (offset != 0 && (offset >> 8) + (offset & 0xFF) <= sectorFree.Count) { + for (int sectorNum = 0; sectorNum < (offset & 0xFF); ++sectorNum) { + sectorFree[(offset >> 8) + sectorNum] = false; + } + } + } + for (int i = 0; i < SECTOR_INTS; ++i) { + byte[] modBytes = new byte[4]; + file.Read(modBytes, 0, 4); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(modBytes); + } + int lastModValue = BitConverter.ToInt32(modBytes, 0); + + chunkTimestamps[i] = lastModValue; + } + //} + } + catch (IOException e) { + System.Console.WriteLine(e.Message); + System.Console.WriteLine(e.StackTrace); + } + } + + /* the modification date of the region file when it was first opened */ + public long LastModified() { + return lastModified; + } + + /* gets how much the region file has grown since it was last checked */ + public int GetSizeDelta() { + int ret = sizeDelta; + sizeDelta = 0; + return ret; + } + + // various small debug printing helpers + private void Debug(String str) { + // System.Consle.Write(str); + } + + private void Debugln(String str) { + Debug(str + "\n"); + } + + private void Debug(String mode, int x, int z, String str) { + Debug("REGION " + mode + " " + fileName + "[" + x + "," + z + "] = " + str); + } + + private void Debug(String mode, int x, int z, int count, String str) { + Debug("REGION " + mode + " " + fileName + "[" + x + "," + z + "] " + count + "B = " + str); + } + + private void Debugln(String mode, int x, int z, String str) { + Debug(mode, x, z, str + "\n"); + } + + /* + * gets an (uncompressed) stream representing the chunk data returns null if + * the chunk is not found or an error occurs + */ + public Stream GetChunkDataInputStream(int x, int z) { + if (OutOfBounds(x, z)) { + Debugln("READ", x, z, "out of bounds"); + return null; + } + + try { + int offset = GetOffset(x, z); + if (offset == 0) { + // Debugln("READ", x, z, "miss"); + return null; + } + + int sectorNumber = offset >> 8; + int numSectors = offset & 0xFF; + + if (sectorNumber + numSectors > sectorFree.Count) { + Debugln("READ", x, z, "invalid sector"); + return null; + } + + file.Seek(sectorNumber * SECTOR_BYTES, SeekOrigin.Begin); + byte[] lengthBytes = new byte[4]; + file.Read(lengthBytes, 0, 4); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(lengthBytes); + } + int length = BitConverter.ToInt32(lengthBytes, 0); + + if (length > SECTOR_BYTES * numSectors) { + Debugln("READ", x, z, "invalid length: " + length + " > 4096 * " + numSectors); + return null; + } + + byte version = (byte)file.ReadByte(); + if (version == VERSION_GZIP) { + byte[] data = new byte[length - 1]; + file.Read(data, 0, data.Length); + Stream ret = new GZipStream(new MemoryStream(data), CompressionMode.Decompress); + // Debug("READ", x, z, " = found"); + return ret; + } else if (version == VERSION_DEFLATE) { + byte[] data = new byte[length - 1]; + file.Read(data, 0, data.Length); + Stream ret = new ZlibStream(new MemoryStream(data), CompressionMode.Decompress, true); + // Debug("READ", x, z, " = found"); + return ret; + } + + Debugln("READ", x, z, "unknown version " + version); + return null; + } catch (IOException) { + Debugln("READ", x, z, "exception"); + return null; + } + } + + public Stream GetChunkDataOutputStream(int x, int z) { + if (OutOfBounds(x, z)) return null; + + return new ZlibStream(new ChunkBuffer(this, x, z), CompressionMode.Compress); + } + + /* + * lets chunk writing be multithreaded by not locking the whole file as a + * chunk is serializing -- only writes when serialization is over + */ + class ChunkBuffer : MemoryStream { + private int x, z; + private RegionFile region; + + public ChunkBuffer(RegionFile r, int x, int z) : base(8096) { + // super(8096); // initialize to 8KB + this.region = r; + this.x = x; + this.z = z; + } + + public override void Close() { + region.Write(x, z, this.GetBuffer(), (int)this.Length); + } + } + + /* write a chunk at (x,z) with length bytes of data to disk */ + protected void Write(int x, int z, byte[] data, int length) { + try { + int offset = GetOffset(x, z); + int sectorNumber = offset >> 8; + int sectorsAllocated = offset & 0xFF; + int sectorsNeeded = (length + CHUNK_HEADER_SIZE) / SECTOR_BYTES + 1; + + // maximum chunk size is 1MB + if (sectorsNeeded >= 256) { + return; + } + + if (sectorNumber != 0 && sectorsAllocated == sectorsNeeded) { + /* we can simply overwrite the old sectors */ + Debug("SAVE", x, z, length, "rewrite"); + Write(sectorNumber, data, length); + } else { + /* we need to allocate new sectors */ + + /* mark the sectors previously used for this chunk as free */ + for (int i = 0; i < sectorsAllocated; ++i) { + sectorFree[sectorNumber + i] = true; + } + + /* scan for a free space large enough to store this chunk */ + int runStart = sectorFree.FindIndex(b => b == true); + int runLength = 0; + if (runStart != -1) { + for (int i = runStart; i < sectorFree.Count; ++i) { + if (runLength != 0) { + if (sectorFree[i]) runLength++; + else runLength = 0; + } else if (sectorFree[i]) { + runStart = i; + runLength = 1; + } + if (runLength >= sectorsNeeded) { + break; + } + } + } + + if (runLength >= sectorsNeeded) { + /* we found a free space large enough */ + Debug("SAVE", x, z, length, "reuse"); + sectorNumber = runStart; + SetOffset(x, z, (sectorNumber << 8) | sectorsNeeded); + for (int i = 0; i < sectorsNeeded; ++i) { + sectorFree[sectorNumber + i] = false; + } + Write(sectorNumber, data, length); + } else { + /* + * no free space large enough found -- we need to grow the + * file + */ + Debug("SAVE", x, z, length, "grow"); + file.Seek(0, SeekOrigin.End); + sectorNumber = sectorFree.Count; + for (int i = 0; i < sectorsNeeded; ++i) { + file.Write(emptySector, 0, emptySector.Length); + sectorFree.Add(false); + } + sizeDelta += SECTOR_BYTES * sectorsNeeded; + + Write(sectorNumber, data, length); + SetOffset(x, z, (sectorNumber << 8) | sectorsNeeded); + } + } + SetTimestamp(x, z, Timestamp()); + } catch (IOException e) { + Console.WriteLine(e.StackTrace); + } + } + + /* write a chunk data to the region file at specified sector number */ + private void Write(int sectorNumber, byte[] data, int length) { + Debugln(" " + sectorNumber); + file.Seek(sectorNumber * SECTOR_BYTES, SeekOrigin.Begin); + + byte[] bytes = BitConverter.GetBytes(length + 1); + if (BitConverter.IsLittleEndian) {; + Array.Reverse(bytes); + } + file.Write(bytes, 0, 4); // chunk length + file.WriteByte(VERSION_DEFLATE); // chunk version number + file.Write(data, 0, length); // chunk data + } + + public void DeleteChunk (int x, int z) + { + int offset = GetOffset(x, z); + int sectorNumber = offset >> 8; + int sectorsAllocated = offset & 0xFF; + + file.Seek(sectorNumber * SECTOR_BYTES, SeekOrigin.Begin); + for (int i = 0; i < sectorsAllocated; i++) { + file.Write(emptySector, 0, SECTOR_BYTES); + } + + SetOffset(x, z, 0); + SetTimestamp(x, z, 0); + } + + /* is this an invalid chunk coordinate? */ + private bool OutOfBounds (int x, int z) + { + return x < 0 || x >= 32 || z < 0 || z >= 32; + } + + private int GetOffset(int x, int z) { + return offsets[x + z * 32]; + } + + public bool HasChunk(int x, int z) { + return GetOffset(x, z) != 0; + } + + private void SetOffset(int x, int z, int offset) { + offsets[x + z * 32] = offset; + file.Seek((x + z * 32) * 4, SeekOrigin.Begin); + + byte[] bytes = BitConverter.GetBytes(offset); + if (BitConverter.IsLittleEndian) {; + Array.Reverse(bytes); + } + + file.Write(bytes, 0, 4); + } + + private int Timestamp () { + DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0); + return (int)((DateTime.UtcNow - epoch).Ticks / (10000L * 1000L)); + } + + private int Timestamp (DateTime time) + { + DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0); + return (int)((time - epoch).Ticks / (10000L * 1000L)); + } + + private void SetTimestamp(int x, int z, int value) { + chunkTimestamps[x + z * 32] = value; + file.Seek(SECTOR_BYTES + (x + z * 32) * 4, SeekOrigin.Begin); + + byte[] bytes = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) {; + Array.Reverse(bytes); + } + + file.Write(bytes, 0, 4); + } + + public void Close() { + file.Close(); + } + } +} diff --git a/Substrate/SubstrateCS/Source/RegionKey.cs b/Substrate/SubstrateCS/Source/RegionKey.cs new file mode 100644 index 0000000..082aee8 --- /dev/null +++ b/Substrate/SubstrateCS/Source/RegionKey.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + public struct RegionKey : IEquatable + { + readonly int rx; + readonly int rz; + + public RegionKey (int _rx, int _rz) + { + rx = _rx; + rz = _rz; + } + + public bool Equals (RegionKey ck) + { + return this.rx == ck.rx && this.rz == ck.rz; + } + + public override bool Equals (Object o) + { + try { + return this == (RegionKey)o; + } + catch { + return false; + } + } + + public override int GetHashCode () + { + int hash = 23; + hash = hash * 37 + rx; + hash = hash * 37 + rz; + return hash; + } + + public static bool operator == (RegionKey k1, RegionKey k2) + { + return k1.rx == k2.rx && k1.rz == k2.rz; + } + + public static bool operator != (RegionKey k1, RegionKey k2) + { + return k1.rx != k2.rx || k1.rz != k2.rz; + } + } +} diff --git a/Substrate/SubstrateCS/Source/RegionManager.cs b/Substrate/SubstrateCS/Source/RegionManager.cs new file mode 100644 index 0000000..f871d90 --- /dev/null +++ b/Substrate/SubstrateCS/Source/RegionManager.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace NBToolkit.Map +{ + public class RegionManager + { + protected string _regionPath; + + protected Dictionary _cache; + + public RegionManager (string regionDir) + { + _regionPath = regionDir; + _cache = new Dictionary(); + } + + public Region GetRegion (int rx, int rz) + { + RegionKey k = new RegionKey(rx, rz); + Region r; + + try { + if (_cache.TryGetValue(k, out r) == false) { + r = new Region(this, rx, rz); + _cache.Add(k, r); + } + return r; + } + catch (FileNotFoundException) { + _cache.Add(k, null); + return null; + } + } + + public Region GetRegion (string filename) + { + int rx, rz; + if (!Region.ParseFileName(filename, out rx, out rz)) { + throw new ArgumentException("Malformed region file name: " + filename, "filename"); + } + + return GetRegion(rx, rz); + } + + public string GetRegionPath () + { + return _regionPath; + } + + public bool DeleteRegion (int rx, int rz) + { + Region r = GetRegion(rx, rz); + if (r == null) { + return false; + } + + RegionKey k = new RegionKey(rx, rz); + _cache.Remove(k); + + r.Dispose(); + + try { + File.Delete(r.GetFilePath()); + } + catch (Exception e) { + Console.WriteLine("NOTICE: " + e.Message); + return false; + } + + return true; + } + } +} diff --git a/Substrate/SubstrateCS/Source/TileEntity.cs b/Substrate/SubstrateCS/Source/TileEntity.cs new file mode 100644 index 0000000..6a7301f --- /dev/null +++ b/Substrate/SubstrateCS/Source/TileEntity.cs @@ -0,0 +1,631 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + using NBT; + using Utility; + + public class TileEntity : INBTObject, ICopyable + { + public static readonly NBTCompoundNode BaseSchema = new NBTCompoundNode("") + { + new NBTScalerNode("id", NBT_Type.TAG_STRING), + new NBTScalerNode("x", NBT_Type.TAG_INT), + new NBTScalerNode("y", NBT_Type.TAG_INT), + new NBTScalerNode("z", NBT_Type.TAG_INT), + }; + + private string _id; + private int _x; + private int _y; + private int _z; + + public string ID + { + get { return _id; } + } + + public int X + { + get { return _x; } + set { _x = value; } + } + + public int Y + { + get { return _y; } + set { _y = value; } + } + + public int Z + { + get { return _z; } + set { _z = value; } + } + + public TileEntity (string id) + { + _id = id; + } + + public TileEntity (TileEntity te) + { + _id = te._id; + _x = te._x; + _y = te._y; + _z = te._z; + } + + public bool LocatedAt (int x, int y, int z) + { + return _x == x && _y == y && _z == z; + } + + #region ICopyable Members + + public virtual TileEntity Copy () + { + return new TileEntity(this); + } + + #endregion + + #region INBTObject Members + + public virtual TileEntity LoadTree (NBT_Value tree) + { + NBT_Compound ctree = tree as NBT_Compound; + if (ctree == null) { + return null; + } + + _id = ctree["id"].ToNBTString(); + _x = ctree["x"].ToNBTInt(); + _y = ctree["y"].ToNBTInt(); + _z = ctree["z"].ToNBTInt(); + + return this; + } + + public virtual TileEntity LoadTreeSafe (NBT_Value tree) + { + if (!ValidateTree(tree)) { + return null; + } + + return LoadTree(tree); + } + + public virtual NBT_Value BuildTree () + { + NBT_Compound tree = new NBT_Compound(); + tree["id"] = new NBT_String(_id); + tree["x"] = new NBT_Int(_x); + tree["y"] = new NBT_Int(_y); + tree["z"] = new NBT_Int(_z); + + return tree; + } + + public virtual bool ValidateTree (NBT_Value tree) + { + return new NBTVerifier(tree, BaseSchema).Verify(); + } + + #endregion + } + + public class TileEntityFurnace : TileEntity, IItemContainer + { + public static readonly NBTCompoundNode FurnaceSchema = BaseSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Furnace"), + new NBTScalerNode("BurnTime", NBT_Type.TAG_SHORT), + new NBTScalerNode("CookTime", NBT_Type.TAG_SHORT), + new NBTListNode("Items", NBT_Type.TAG_COMPOUND, ItemCollection.ListSchema), + }); + + private const int _CAPACITY = 3; + + private short _burnTime; + private short _cookTime; + + private ItemCollection _items; + + public int BurnTime + { + get { return _burnTime; } + set { _burnTime = (short)value; } + } + + public int CookTime + { + get { return _cookTime; } + set { _cookTime = (short)value; } + } + + public TileEntityFurnace () + : base("Furnace") + { + _items = new ItemCollection(_CAPACITY); + } + + public TileEntityFurnace (TileEntity te) + : base (te) + { + TileEntityFurnace tec = te as TileEntityFurnace; + if (tec != null) { + _cookTime = tec._cookTime; + _burnTime = tec._burnTime; + _items = tec._items.Copy(); + } + else { + _items = new ItemCollection(_CAPACITY); + } + } + + #region ICopyable Members + + public override TileEntity Copy () + { + return new TileEntityFurnace(this); + } + + #endregion + + #region IItemContainer Members + + public ItemCollection Items + { + get { return _items; } + } + + #endregion + + #region INBTObject Members + + public override TileEntity LoadTree (NBT_Value tree) + { + NBT_Compound ctree = tree as NBT_Compound; + if (ctree == null || base.LoadTree(tree) == null) { + return null; + } + + _burnTime = ctree["BurnTime"].ToNBTShort(); + _cookTime = ctree["CookTime"].ToNBTShort(); + + NBT_List items = ctree["Items"].ToNBTList(); + _items = new ItemCollection(_CAPACITY).LoadTree(items); + + return this; + } + + public override NBT_Value BuildTree () + { + NBT_Compound tree = base.BuildTree() as NBT_Compound; + tree["BurnTime"] = new NBT_Short(_burnTime); + tree["CookTime"] = new NBT_Short(_cookTime); + tree["Items"] = _items.BuildTree(); + + return tree; + } + + public override bool ValidateTree (NBT_Value tree) + { + return new NBTVerifier(tree, FurnaceSchema).Verify(); + } + + #endregion + } + + public class TileEntitySign : TileEntity + { + public static readonly NBTCompoundNode SignSchema = BaseSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Sign"), + new NBTScalerNode("Text1", NBT_Type.TAG_STRING), + new NBTScalerNode("Text2", NBT_Type.TAG_STRING), + new NBTScalerNode("Text3", NBT_Type.TAG_STRING), + new NBTScalerNode("Text4", NBT_Type.TAG_STRING), + }); + + private string _text1 = ""; + private string _text2 = ""; + private string _text3 = ""; + private string _text4 = ""; + + public string Text1 + { + get { return _text1; } + set { _text1 = value.Substring(0, 12); } + } + + public string Text2 + { + get { return _text2; } + set { _text2 = value.Substring(0, 12); } + } + + public string Text3 + { + get { return _text3; } + set { _text3 = value.Substring(0, 12); } + } + + public string Text4 + { + get { return _text4; } + set { _text4 = value.Substring(0, 12); } + } + + public TileEntitySign () + : base("Sign") + { + } + + public TileEntitySign (TileEntity te) + : base(te) + { + TileEntitySign tes = te as TileEntitySign; + if (tes != null) { + _text1 = tes._text1; + _text2 = tes._text2; + _text3 = tes._text3; + _text4 = tes._text4; + } + } + + #region ICopyable Members + + public override TileEntity Copy () + { + return new TileEntitySign(this); + } + + #endregion + + #region INBTObject Members + + public override TileEntity LoadTree (NBT_Value tree) + { + NBT_Compound ctree = tree as NBT_Compound; + if (ctree == null || base.LoadTree(tree) == null) { + return null; + } + + _text1 = ctree["Text1"].ToNBTString(); + _text2 = ctree["Text2"].ToNBTString(); + _text3 = ctree["Text3"].ToNBTString(); + _text4 = ctree["Text4"].ToNBTString(); + + return this; + } + + public override NBT_Value BuildTree () + { + NBT_Compound tree = base.BuildTree() as NBT_Compound; + tree["Text1"] = new NBT_String(_text1); + tree["Text2"] = new NBT_String(_text2); + tree["Text3"] = new NBT_String(_text3); + tree["Text4"] = new NBT_String(_text4); + + return tree; + } + + public override bool ValidateTree (NBT_Value tree) + { + return new NBTVerifier(tree, SignSchema).Verify(); + } + + #endregion + } + + public class TileEntityMobSpawner : TileEntity + { + public static readonly NBTCompoundNode MobSpawnerSchema = BaseSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "MobSpawner"), + new NBTScalerNode("EntityId", NBT_Type.TAG_STRING), + new NBTScalerNode("Delay", NBT_Type.TAG_SHORT), + }); + + private short _delay; + private string _entityID; + + public int Delay + { + get { return _delay; } + set { _delay = (short)value; } + } + + public string EntityID + { + get { return _entityID; } + set { _entityID = value; } + } + + public TileEntityMobSpawner () + : base("MobSpawner") + { + } + + public TileEntityMobSpawner (TileEntity te) + : base(te) + { + TileEntityMobSpawner tes = te as TileEntityMobSpawner; + if (tes != null) { + _delay = tes._delay; + _entityID = tes._entityID; + } + } + + #region ICopyable Members + + public override TileEntity Copy () + { + return new TileEntityMobSpawner(this); + } + + #endregion + + #region INBTObject Members + + public override TileEntity LoadTree (NBT_Value tree) + { + NBT_Compound ctree = tree as NBT_Compound; + if (ctree == null || base.LoadTree(tree) == null) { + return null; + } + + _delay = ctree["Delay"].ToNBTShort(); + _entityID = ctree["EntityID"].ToNBTString(); + + return this; + } + + public override NBT_Value BuildTree () + { + NBT_Compound tree = base.BuildTree() as NBT_Compound; + tree["EntityID"] = new NBT_String(_entityID); + tree["Delay"] = new NBT_Short(_delay); + + return tree; + } + + public override bool ValidateTree (NBT_Value tree) + { + return new NBTVerifier(tree, MobSpawnerSchema).Verify(); + } + + #endregion + } + + public class TileEntityChest : TileEntity, IItemContainer + { + public static readonly NBTCompoundNode ChestSchema = BaseSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Chest"), + new NBTListNode("Items", NBT_Type.TAG_COMPOUND, ItemCollection.ListSchema), + }); + + private const int _CAPACITY = 27; + + private ItemCollection _items; + + public TileEntityChest () + : base("Chest") + { + _items = new ItemCollection(_CAPACITY); + } + + public TileEntityChest (TileEntity te) + : base(te) + { + TileEntityChest tec = te as TileEntityChest; + if (tec != null) { + _items = tec._items.Copy(); + } + else { + _items = new ItemCollection(_CAPACITY); + } + } + + #region ICopyable Members + + public override TileEntity Copy () + { + return new TileEntityChest(this); + } + + #endregion + + #region IItemContainer Members + + public ItemCollection Items + { + get { return _items; } + } + + #endregion + + #region INBTObject Members + + public override TileEntity LoadTree (NBT_Value tree) + { + NBT_Compound ctree = tree as NBT_Compound; + if (ctree == null || base.LoadTree(tree) == null) { + return null; + } + + NBT_List items = ctree["Items"].ToNBTList(); + _items = new ItemCollection(_CAPACITY).LoadTree(items); + + return this; + } + + public override NBT_Value BuildTree () + { + NBT_Compound tree = base.BuildTree() as NBT_Compound; + tree["Items"] = _items.BuildTree(); + + return tree; + } + + public override bool ValidateTree (NBT_Value tree) + { + return new NBTVerifier(tree, ChestSchema).Verify(); + } + + #endregion + } + + public class TileEntityMusic : TileEntity + { + public static readonly NBTCompoundNode MusicSchema = BaseSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Music"), + new NBTScalerNode("note", NBT_Type.TAG_BYTE), + }); + + private byte _note; + + public int Note + { + get { return _note; } + set { _note = (byte)value; } + } + + public TileEntityMusic () + : base("Music") + { + } + + public TileEntityMusic (TileEntity te) + : base(te) + { + TileEntityMusic tes = te as TileEntityMusic; + if (tes != null) { + _note = tes._note; + } + } + + #region ICopyable Members + + public override TileEntity Copy () + { + return new TileEntityMusic(this); + } + + #endregion + + #region INBTObject Members + + public override TileEntity LoadTree (NBT_Value tree) + { + NBT_Compound ctree = tree as NBT_Compound; + if (ctree == null || base.LoadTree(tree) == null) { + return null; + } + + _note = ctree["Note"].ToNBTByte(); + + return this; + } + + public override NBT_Value BuildTree () + { + NBT_Compound tree = base.BuildTree() as NBT_Compound; + tree["Note"] = new NBT_Byte(_note); + + return tree; + } + + public override bool ValidateTree (NBT_Value tree) + { + return new NBTVerifier(tree, MusicSchema).Verify(); + } + + #endregion + } + + public class TileEntityTrap : TileEntity, IItemContainer + { + public static readonly NBTCompoundNode TrapSchema = BaseSchema.MergeInto(new NBTCompoundNode("") + { + new NBTStringNode("id", "Trap"), + new NBTListNode("Items", NBT_Type.TAG_COMPOUND, ItemCollection.ListSchema), + }); + + private const int _CAPACITY = 8; + + private ItemCollection _items; + + public TileEntityTrap () + : base("Trap") + { + _items = new ItemCollection(_CAPACITY); + } + + public TileEntityTrap (TileEntity te) + : base(te) + { + TileEntityTrap tec = te as TileEntityTrap; + if (tec != null) { + _items = tec._items.Copy(); + } + else { + _items = new ItemCollection(_CAPACITY); + } + } + + #region ICopyable Members + + public override TileEntity Copy () + { + return new TileEntityTrap(this); + } + + #endregion + + #region IItemContainer Members + + public ItemCollection Items + { + get { return _items; } + } + + #endregion + + #region INBTObject Members + + public override TileEntity LoadTree (NBT_Value tree) + { + NBT_Compound ctree = tree as NBT_Compound; + if (ctree == null || base.LoadTree(tree) == null) { + return null; + } + + NBT_List items = ctree["Items"].ToNBTList(); + _items = new ItemCollection(_CAPACITY).LoadTree(items); + + return this; + } + + public override NBT_Value BuildTree () + { + NBT_Compound tree = base.BuildTree() as NBT_Compound; + tree["Items"] = _items.BuildTree(); + + return tree; + } + + public override bool ValidateTree (NBT_Value tree) + { + return new NBTVerifier(tree, TrapSchema).Verify(); + } + + #endregion + } +} diff --git a/Substrate/SubstrateCS/Source/TileEntityFactory.cs b/Substrate/SubstrateCS/Source/TileEntityFactory.cs new file mode 100644 index 0000000..00be6e6 --- /dev/null +++ b/Substrate/SubstrateCS/Source/TileEntityFactory.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map +{ + using NBT; + + public class TileEntityFactory + { + private static Dictionary _registry; + + public static TileEntity Create (string type) + { + Type t; + if (!_registry.TryGetValue(type, out t)) { + return null; + } + + return Activator.CreateInstance(t) as TileEntity; + } + + public static TileEntity Create (NBT_Compound tree) + { + string type = tree["id"].ToNBTString(); + + Type t; + if (!_registry.TryGetValue(type, out t)) { + return null; + } + + TileEntity te = Activator.CreateInstance(t, new object[] { tree }) as TileEntity; + + return te.LoadTreeSafe(tree); + } + + public static Type Lookup (string type) + { + Type t; + if (!_registry.TryGetValue(type, out t)) { + return null; + } + + return t; + } + + public static void Register (string id, Type subtype) + { + _registry[id] = subtype; + } + + static TileEntityFactory () + { + _registry = new Dictionary(); + + _registry["Chest"] = typeof(TileEntityChest); + _registry["Furnace"] = typeof(TileEntityFurnace); + _registry["MobSpawner"] = typeof(TileEntityMobSpawner); + _registry["Music"] = typeof(TileEntityMusic); + _registry["Sign"] = typeof(TileEntitySign); + _registry["Trap"] = typeof(TileEntityTrap); + } + } +} diff --git a/Substrate/SubstrateCS/Source/Utility/Interface.cs b/Substrate/SubstrateCS/Source/Utility/Interface.cs new file mode 100644 index 0000000..7747757 --- /dev/null +++ b/Substrate/SubstrateCS/Source/Utility/Interface.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map.Utility +{ + public interface ICopyable + { + T Copy (); + } +} diff --git a/Substrate/SubstrateCS/Source/Utility/NibbleArray.cs b/Substrate/SubstrateCS/Source/Utility/NibbleArray.cs new file mode 100644 index 0000000..ffecf27 --- /dev/null +++ b/Substrate/SubstrateCS/Source/Utility/NibbleArray.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace NBToolkit.Map.Utility +{ + public class NibbleArray : ICopyable + { + protected readonly byte[] _data = null; + + public NibbleArray (byte[] data) + { + _data = data; + } + + public int this[int index] + { + get + { + int subs = index >> 1; + if ((index & 1) == 0) { + return _data[subs] & 0x0F; + } + else { + return (_data[subs] >> 4) & 0x0F; + } + } + + set + { + int subs = index >> 1; + if ((index & 1) == 0) { + _data[subs] = (byte)((_data[subs] & 0xF0) | (value & 0x0F)); + } + else { + _data[subs] = (byte)((_data[subs] & 0x0F) | ((value & 0x0F) << 4)); + } + } + } + + public int Length + { + get + { + return _data.Length << 1; + } + } + + #region ICopyable Members + + public NibbleArray Copy () + { + byte[] data = new byte[_data.Length]; + _data.CopyTo(data, 0); + + return new NibbleArray(data); + } + + #endregion + } +} diff --git a/Substrate/SubstrateCS/Source/World.cs b/Substrate/SubstrateCS/Source/World.cs new file mode 100644 index 0000000..49d7978 --- /dev/null +++ b/Substrate/SubstrateCS/Source/World.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace NBToolkit.Map +{ + public class World + { + protected RegionManager _regionMan; + protected ChunkManager _chunkMan; + protected BlockManager _blockMan; + + protected string _worldPath; + + public World (string world) + { + _worldPath = world; + + _regionMan = new RegionManager(Path.Combine(world, "region")); + _chunkMan = new ChunkManager(_regionMan); + _blockMan = new BlockManager(_chunkMan); + } + + public RegionManager GetRegionManager () + { + return _regionMan; + } + + public ChunkManager GetChunkManager () + { + return _chunkMan; + } + + public BlockManager GetBlockManager () + { + return _blockMan; + } + } +}