// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT using System; using ClassicalSharp.Events; using ClassicalSharp.Renderers; using OpenTK; namespace ClassicalSharp.Map { /// Represents a fixed size map of blocks. Stores the raw block data, /// heightmap, dimensions and various metadata such as environment settings. public sealed partial class World { Game game; BlockInfo info; internal byte[] blocks; public int Width, Height, Length; internal short[] heightmap; int maxY, oneY; /// Contains the environment metadata for this world. public WorldEnv Env; /// Unique uuid/guid of this particular world. public Guid Uuid; /// Whether this map is empty. public bool IsNotLoaded = true; /// Current terrain.png or texture pack url of this map. public string TextureUrl = null; public World(Game game) { Env = new WorldEnv(game); this.game = game; info = game.BlockInfo; } /// Resets all of the properties to their defaults and raises the 'OnNewMap' event. public void Reset() { Env.Reset(); Width = Height = Length = 0; IsNotLoaded = true; Uuid = Guid.NewGuid(); game.WorldEvents.RaiseOnNewMap(); } /// Updates the underlying block array, heightmap, and dimensions of this map. public void SetNewMap(byte[] blocks, int width, int height, int length) { this.blocks = blocks; this.Width = width; this.Height = height; this.Length = length; IsNotLoaded = width == 0 || length == 0 || height == 0; if (blocks.Length != (width * height * length)) throw new InvalidOperationException("Blocks array length does not match volume of map."); if (Env.EdgeHeight == -1) Env.EdgeHeight = height / 2; maxY = height - 1; oneY = length * width; if (Env.CloudHeight == -1) Env.CloudHeight = height + 2; heightmap = new short[width * length]; for (int i = 0; i < heightmap.Length; i++) heightmap[i] = short.MaxValue; } /// Sets the block at the given world coordinates without bounds checking, /// and also recalculates the heightmap for the given (x,z) column. public void SetBlock(int x, int y, int z, byte blockId) { int index = (y * Length + z) * Width + x; byte oldBlock = blocks[index]; blocks[index] = blockId; UpdateHeight(x, y, z, oldBlock, blockId); WeatherRenderer weather = game.WeatherRenderer; if (weather.heightmap != null && !IsNotLoaded) weather.UpdateHeight(x, y, z, oldBlock, blockId); } /// Sets the block at the given world coordinates without bounds checking, /// and also recalculates the heightmap for the given (x,z) column. public void SetBlock(Vector3I p, byte blockId) { SetBlock(p.X, p.Y, p.Z, blockId); } /// Returns the block at the given world coordinates without bounds checking. public byte GetBlock(int x, int y, int z) { return blocks[(y * Length + z) * Width + x]; } /// Returns the block at the given world coordinates without bounds checking. public byte GetBlock(Vector3I p) { return blocks[(p.Y * Length + p.Z) * Width + p.X]; } /// Returns the block at the given world coordinates with bounds checking, /// returning 0 is the coordinates were outside the map. public byte SafeGetBlock(int x, int y, int z) { return IsValidPos(x, y, z) ? blocks[(y * Length + z) * Width + x] : Block.Air; } /// Returns the block at the given world coordinates with bounds checking, /// returning 0 is the coordinates were outside the map. public byte SafeGetBlock(Vector3I p) { return IsValidPos(p.X, p.Y, p.Z) ? blocks[(p.Y * Length + p.Z) * Width + p.X] : Block.Air; } /// Returns whether the given world coordinates are contained /// within the dimensions of the map. public bool IsValidPos(int x, int y, int z) { return x >= 0 && y >= 0 && z >= 0 && x < Width && y < Height && z < Length; } /// Returns whether the given world coordinates are contained /// within the dimensions of the map. public bool IsValidPos(Vector3I p) { return p.X >= 0 && p.Y >= 0 && p.Z >= 0 && p.X < Width && p.Y < Height && p.Z < Length; } /// Returns whether the given world coordinates are fully not in sunlight. public bool IsLit(int x, int y, int z) { if (!IsValidPos(x, y, z)) return true; return y > GetLightHeight(x, z); } /// Returns whether the given world coordinatse are fully not in sunlight. public bool IsLit(Vector3I p) { if (!IsValidPos(p.X, p.Y, p.Z)) return true; return p.Y > GetLightHeight(p.X, p.Z); } /// Returns whether the given world coordinatse are fully not in sunlight. public bool IsLit(Vector3 p) { int x = Utils.Floor(p.X), y = Utils.Floor(p.Y), z = Utils.Floor(p.Z); if (!IsValidPos(x, y, z)) return true; return y > GetLightHeight(x, z); } /// Returns the y coordinate of the highest block that is fully not in sunlight. /// e.g. if cobblestone was at y = 5, this method would return 4. public int GetLightHeight(int x, int z) { int index = (z * Width) + x; int height = heightmap[index]; return height == short.MaxValue ? CalcHeightAt(x, maxY, z, index) : height; } /// Unpacks the given index into the map's block array into its original world coordinates. public Vector3I GetCoords(int index) { if (index < 0 || index >= blocks.Length) return new Vector3I(-1); int x = index % Width; int y = index / oneY; // index / (width * length) int z = (index / Width) % Length; return new Vector3I(x, y, z); } public byte GetPhysicsBlock(int x, int y, int z) { if (x < 0 || x >= Width || z < 0 || z >= Length || y < 0) return Block.Bedrock; if (y >= Height) return Block.Air; return blocks[(y * Length + z) * Width + x]; } } }