// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT using System; using System.IO; using System.IO.Compression; using ClassicalSharp.Entities; using OpenTK; namespace ClassicalSharp.Map { public sealed class MapCwExporter : IMapFormatExporter { BinaryWriter writer; NbtFile nbt; Game game; World map; public void Save(Stream stream, Game game) { using (GZipStream wrapper = new GZipStream(stream, CompressionMode.Compress)) { writer = new BinaryWriter(wrapper); nbt = new NbtFile(writer); this.game = game; map = game.World; nbt.Write(NbtTagType.Compound); nbt.Write("ClassicWorld"); nbt.Write(NbtTagType.Int8); nbt.Write("FormatVersion"); nbt.WriteInt8(1); nbt.Write(NbtTagType.Int8Array); nbt.Write("UUID"); nbt.WriteInt32(16); nbt.WriteBytes(map.Uuid.ToByteArray()); nbt.Write(NbtTagType.Int16); nbt.Write("X"); nbt.WriteInt16((short)map.Width); nbt.Write(NbtTagType.Int16); nbt.Write("Y"); nbt.WriteInt16((short)map.Height); nbt.Write(NbtTagType.Int16); nbt.Write("Z"); nbt.WriteInt16((short)map.Length); WriteSpawnCompoundTag(); nbt.Write(NbtTagType.Int8Array); nbt.Write("BlockArray"); nbt.WriteInt32(map.blocks.Length); nbt.WriteBytes(map.blocks); WriteMetadata(); nbt.Write(NbtTagType.End); } } void WriteSpawnCompoundTag() { nbt.Write(NbtTagType.Compound); nbt.Write("Spawn"); LocalPlayer p = game.LocalPlayer; Vector3 spawn = p.Position; // TODO: Maybe also keep real spawn too? nbt.Write(NbtTagType.Int16); nbt.Write("X"); nbt.WriteInt16((short)spawn.X); nbt.Write(NbtTagType.Int16); nbt.Write("Y"); nbt.WriteInt16((short)spawn.Y); nbt.Write(NbtTagType.Int16); nbt.Write("Z"); nbt.WriteInt16((short)spawn.Z); nbt.Write(NbtTagType.Int8); nbt.Write("H"); nbt.WriteUInt8((byte)Utils.DegreesToPacked(p.SpawnYaw)); nbt.Write(NbtTagType.Int8); nbt.Write("P"); nbt.WriteUInt8((byte)Utils.DegreesToPacked(p.SpawnPitch)); nbt.Write(NbtTagType.End); } void WriteMetadata() { nbt.Write(NbtTagType.Compound); nbt.Write("Metadata"); nbt.Write(NbtTagType.Compound); nbt.Write("CPE"); LocalPlayer p = game.LocalPlayer; nbt.WriteCpeExtCompound("ClickDistance", 1); nbt.Write(NbtTagType.Int16); nbt.Write("Distance"); nbt.WriteInt16((short)(p.ReachDistance * 32)); nbt.Write(NbtTagType.End); nbt.WriteCpeExtCompound("EnvWeatherType", 1); nbt.Write(NbtTagType.Int8); nbt.Write("WeatherType"); nbt.WriteUInt8((byte)map.Env.Weather); nbt.Write(NbtTagType.End); nbt.WriteCpeExtCompound("EnvMapAppearance", 1); nbt.Write(NbtTagType.Int8); nbt.Write("SideBlock"); nbt.WriteUInt8(map.Env.SidesBlock); nbt.Write(NbtTagType.Int8); nbt.Write("EdgeBlock"); nbt.WriteUInt8(map.Env.EdgeBlock); nbt.Write(NbtTagType.Int16); nbt.Write("SideLevel"); nbt.WriteInt16((short)map.Env.EdgeHeight); nbt.Write(NbtTagType.String); string url = game.World.TextureUrl == null ? "" : game.World.TextureUrl; nbt.Write("TextureURL"); nbt.Write(url); nbt.Write(NbtTagType.End); nbt.WriteCpeExtCompound("EnvColors", 1); WriteColourCompound("Sky", map.Env.SkyCol); WriteColourCompound("Cloud", map.Env.CloudsCol); WriteColourCompound("Fog", map.Env.FogCol); WriteColourCompound("Ambient", map.Env.Shadowlight); WriteColourCompound("Sunlight", map.Env.Sunlight); nbt.Write(NbtTagType.End); nbt.WriteCpeExtCompound("BlockDefinitions", 1); uint[] flags = game.BlockInfo.DefinedCustomBlocks; for (int block = 1; block < 256; block++) { if ((flags[block >> 5] & (1u << (block & 0x1F))) != 0) WriteBlockDefinitionCompound((byte)block); } nbt.Write(NbtTagType.End); nbt.Write(NbtTagType.End); nbt.Write(NbtTagType.End); } void WriteColourCompound(string name, FastColour col) { nbt.Write(NbtTagType.Compound); nbt.Write(name); nbt.Write(NbtTagType.Int16); nbt.Write("R"); nbt.WriteInt16(col.R); nbt.Write(NbtTagType.Int16); nbt.Write("G"); nbt.WriteInt16(col.G); nbt.Write(NbtTagType.Int16); nbt.Write("B"); nbt.WriteInt16(col.B); nbt.Write(NbtTagType.End); } unsafe void WriteBlockDefinitionCompound(byte id) { BlockInfo info = game.BlockInfo; nbt.Write(NbtTagType.Compound); nbt.Write("Block" + id); nbt.Write(NbtTagType.Int8); nbt.Write("ID"); nbt.WriteUInt8(id); nbt.Write(NbtTagType.String); nbt.Write("Name"); nbt.Write(info.Name[id]); nbt.Write(NbtTagType.Int8); nbt.Write("CollideType"); nbt.WriteUInt8((byte)info.Collide[id]); float speed = info.SpeedMultiplier[id]; nbt.Write(NbtTagType.Real32); nbt.Write("Speed"); nbt.WriteInt32(*((int*)&speed)); nbt.Write(NbtTagType.Int8Array); nbt.Write("Textures"); nbt.WriteInt32(6); nbt.WriteUInt8(info.GetTextureLoc(id, Side.Top)); nbt.WriteUInt8(info.GetTextureLoc(id, Side.Bottom)); nbt.WriteUInt8(info.GetTextureLoc(id, Side.Left)); nbt.WriteUInt8(info.GetTextureLoc(id, Side.Right)); nbt.WriteUInt8(info.GetTextureLoc(id, Side.Front)); nbt.WriteUInt8(info.GetTextureLoc(id, Side.Back)); nbt.Write(NbtTagType.Int8); nbt.Write("TransmitsLight"); nbt.WriteUInt8(info.BlocksLight[id] ? 0 : 1); nbt.Write(NbtTagType.Int8); nbt.Write("WalkSound"); nbt.WriteUInt8((byte)info.DigSounds[id]); nbt.Write(NbtTagType.Int8); nbt.Write("FullBright"); nbt.WriteUInt8(info.FullBright[id] ? 1 : 0); nbt.Write(NbtTagType.Int8); nbt.Write("Shape"); nbt.WriteUInt8(GetShape(info, id)); nbt.Write(NbtTagType.Int8); nbt.Write("BlockDraw"); nbt.WriteUInt8(GetDraw(info, id)); FastColour col = info.FogColour[id]; nbt.Write(NbtTagType.Int8Array); nbt.Write("Fog"); nbt.WriteInt32(4); byte fog = (byte)(128 * info.FogDensity[id] - 1); nbt.WriteUInt8(info.FogDensity[id] == 0 ? (byte)0 : fog); nbt.WriteUInt8(col.R); nbt.WriteUInt8(col.G); nbt.WriteUInt8(col.B); Vector3 min = info.MinBB[id], max = info.MaxBB[id]; nbt.Write(NbtTagType.Int8Array); nbt.Write("Coords"); nbt.WriteInt32(6); nbt.WriteUInt8((byte)(min.X * 16)); nbt.WriteUInt8((byte)(min.Y * 16)); nbt.WriteUInt8((byte)(min.Z * 16)); nbt.WriteUInt8((byte)(max.X * 16)); nbt.WriteUInt8((byte)(max.Y * 16)); nbt.WriteUInt8((byte)(max.Z * 16)); nbt.Write(NbtTagType.End); } int GetShape(BlockInfo info, byte id) { return info.Draw[id] == DrawType.Sprite ? 0 : (int)(info.MaxBB[id].Y * 16); } int GetDraw(BlockInfo info, byte id) { if (info.Draw[id] == DrawType.Sprite) return DrawType.Transparent; return info.Draw[id]; } } }