From f5878f70a3d8efaa01c7c0fea60f5f080dcae1b7 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 8 May 2016 15:39:49 +1000 Subject: [PATCH] Create .lvl and .fcm level exporter. --- ClassicalSharp/ClassicalSharp.csproj | 4 +- .../{IMapFileFormat.cs => IMapFormat.cs} | 0 ClassicalSharp/Map/Formats/MapCw.Exporter.cs | 4 +- .../Map/Formats/MapFcm3.Exporter.cs | 69 ++++++++++++++++++ ClassicalSharp/Map/Formats/MapLvl.Exporter.cs | 72 +++++++++++++++++++ ClassicalSharp/Map/Formats/MapLvl.Importer.cs | 8 ++- .../Network/NetworkProcessor.Original.cs | 4 +- ClassicalSharp/Utils/Utils.cs | 4 ++ 8 files changed, 157 insertions(+), 8 deletions(-) rename ClassicalSharp/Map/Formats/{IMapFileFormat.cs => IMapFormat.cs} (100%) create mode 100644 ClassicalSharp/Map/Formats/MapFcm3.Exporter.cs create mode 100644 ClassicalSharp/Map/Formats/MapLvl.Exporter.cs diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj index 37a11630f..0b9eec11d 100644 --- a/ClassicalSharp/ClassicalSharp.csproj +++ b/ClassicalSharp/ClassicalSharp.csproj @@ -174,6 +174,8 @@ + + @@ -208,7 +210,7 @@ - + diff --git a/ClassicalSharp/Map/Formats/IMapFileFormat.cs b/ClassicalSharp/Map/Formats/IMapFormat.cs similarity index 100% rename from ClassicalSharp/Map/Formats/IMapFileFormat.cs rename to ClassicalSharp/Map/Formats/IMapFormat.cs diff --git a/ClassicalSharp/Map/Formats/MapCw.Exporter.cs b/ClassicalSharp/Map/Formats/MapCw.Exporter.cs index 95ec32aeb..5cd5db625 100644 --- a/ClassicalSharp/Map/Formats/MapCw.Exporter.cs +++ b/ClassicalSharp/Map/Formats/MapCw.Exporter.cs @@ -67,11 +67,11 @@ namespace ClassicalSharp.Map { nbt.Write( NbtTagType.Int8 ); nbt.Write( "H" ); - nbt.WriteUInt8( (byte)Utils.DegreesToPacked( p.SpawnYaw, 256 ) ); + nbt.WriteUInt8( (byte)Utils.DegreesToPacked( p.SpawnYaw ) ); nbt.Write( NbtTagType.Int8 ); nbt.Write( "P" ); - nbt.WriteUInt8( (byte)Utils.DegreesToPacked( p.SpawnPitch, 256 ) ); + nbt.WriteUInt8( (byte)Utils.DegreesToPacked( p.SpawnPitch ) ); nbt.Write( NbtTagType.End ); } diff --git a/ClassicalSharp/Map/Formats/MapFcm3.Exporter.cs b/ClassicalSharp/Map/Formats/MapFcm3.Exporter.cs new file mode 100644 index 000000000..f6ff7509a --- /dev/null +++ b/ClassicalSharp/Map/Formats/MapFcm3.Exporter.cs @@ -0,0 +1,69 @@ +// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT +// Part of fCraft | Copyright (c) 2009-2014 Matvei Stefarov | BSD-3 | See LICENSE.txt +using System; +using System.IO; +using System.IO.Compression; +using System.Text; +using ClassicalSharp.Entities; + +namespace ClassicalSharp.Map { + + /// Exports a world to a FCMv3 map file (fCraft server map) + public sealed class MapFcm3Exporter : IMapFormatExporter { + + const uint Identifier = 0x0FC2AF40; + const byte Revision = 13; + const int chunkSize = 128 * 128 * 128; + + public void Save( Stream stream, Game game ) { + World map = game.World; + LocalPlayer p = game.LocalPlayer; + BinaryWriter writer = new BinaryWriter( stream ); + writer.Write( Identifier ); + writer.Write( Revision ); + + writer.Write( (short)map.Width ); + writer.Write( (short)map.Height ); + writer.Write( (short)map.Length ); + + writer.Write( (int)(p.Spawn.X * 32) ); + writer.Write( (int)(p.Spawn.Y * 32) ); + writer.Write( (int)(p.Spawn.Z * 32) ); + + writer.Write( Utils.DegreesToPacked( p.SpawnYaw ) ); + writer.Write( Utils.DegreesToPacked( p.SpawnPitch ) ); + + writer.Write( 0 ); // Date modified + writer.Write( 0 ); // Date created + + writer.Write( map.Uuid.ToByteArray() ); + writer.Write( (byte)1 ); // layer count + + // skip over index and metacount + long indexOffset = stream.Position; + writer.Seek( 29, SeekOrigin.Current ); + long offset = stream.Position; + byte[] blocks = map.mapData; + + using( DeflateStream ds = new DeflateStream( stream, CompressionMode.Compress, true ) ) { + for( int i = 0; i < blocks.Length; i += chunkSize ) { + int len = Math.Min( chunkSize, blocks.Length - i ); + ds.Write( blocks, i, len ); + } + } + int compressedLength = (int)(stream.Position - offset); + + // come back to write the index + writer.BaseStream.Seek( indexOffset, SeekOrigin.Begin ); + + writer.Write( (byte)0 ); // data layer type (Blocks) + writer.Write( offset ); // offset, in bytes, from start of stream + writer.Write( compressedLength ); // compressed length, in bytes + writer.Write( 0 ); // general purpose field + writer.Write( 1 ); // element size + writer.Write( map.mapData.Length ); // element count + + writer.Write( 0 ); // No metadata + } + } +} \ No newline at end of file diff --git a/ClassicalSharp/Map/Formats/MapLvl.Exporter.cs b/ClassicalSharp/Map/Formats/MapLvl.Exporter.cs new file mode 100644 index 000000000..c60a37999 --- /dev/null +++ b/ClassicalSharp/Map/Formats/MapLvl.Exporter.cs @@ -0,0 +1,72 @@ +// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT +using System; +using System.IO; +using System.IO.Compression; +using System.Text; +using ClassicalSharp.Entities; +using ClassicalSharp.Net; + +namespace ClassicalSharp.Map { + + /// Exports a world to a LVL map file (MCLawl server map) + public sealed class MapLvlExporter : IMapFormatExporter { + + const int Version = 1874; + const byte customTile = 163; + + public void Save( Stream stream, Game game ) { + World map = game.World; + LocalPlayer p = game.LocalPlayer; + const int chunkSize = 128 * 128 * 128; + + using( DeflateStream gs = new DeflateStream( stream, CompressionMode.Compress ) ) { + BinaryWriter writer = new BinaryWriter( gs ); + + writer.Write( (ushort)Version ); + writer.Write( (ushort)map.Width ); + writer.Write( (ushort)map.Length ); + writer.Write( (ushort)map.Height ); + + writer.Write( (ushort)p.Spawn.X ); + writer.Write( (ushort)p.Spawn.Z ); + writer.Write( (ushort)p.Spawn.Y ); + writer.Write( Utils.DegreesToPacked( p.SpawnYaw ) ); + writer.Write( Utils.DegreesToPacked( p.SpawnPitch ) ); + + writer.Write( (ushort)0 ); // pervisit and perbuild perms + WriteBlocks( map.mapData, writer ); + + writer.Write( (byte)0xBD ); + WriteCustomBlocks( map, writer ); + } + } + + void WriteBlocks( byte[] blocks, BinaryWriter writer ) { + const int bufferSize = 64 * 1024; + byte[] buffer = new byte[bufferSize]; + int bIndex = 0; + + for( int i = 0; i < blocks.Length; i++ ) { + byte block = blocks[i]; + buffer[bIndex] = block >= BlockInfo.CpeBlocksCount ? customTile : block; + + bIndex++; + if( bIndex == bufferSize ) { + writer.Write( buffer, 0, bufferSize ); bIndex = 0; + } + } + if( bIndex > 0 ) writer.Write( buffer, 0, bIndex ); + } + + void WriteCustomBlocks( World map, BinaryWriter writer ) { + byte[] chunk = new byte[16 * 16 * 16]; + + for( int y = 0; y < map.Height; y += 16 ) + for( int z = 0; z < map.Length; z += 16 ) + for( int x = 0; x < map.Width; x += 16 ) + { + writer.Write( (byte)0 ); + } + } + } +} \ No newline at end of file diff --git a/ClassicalSharp/Map/Formats/MapLvl.Importer.cs b/ClassicalSharp/Map/Formats/MapLvl.Importer.cs index bff92d4ec..fb33c8687 100644 --- a/ClassicalSharp/Map/Formats/MapLvl.Importer.cs +++ b/ClassicalSharp/Map/Formats/MapLvl.Importer.cs @@ -13,6 +13,8 @@ namespace ClassicalSharp.Map { public sealed class MapLvlImporter : IMapFormatImporter { const int Version = 1874; + const byte customTile = 163; + public byte[] Load( Stream stream, Game game, out int width, out int height, out int length ) { GZipHeaderReader gsHeader = new GZipHeaderReader(); while( !gsHeader.ReadHeader( stream ) ) { } @@ -59,7 +61,7 @@ namespace ClassicalSharp.Map { int bx = i & 0xF, by = (i >> 8) & 0xF, bz = (i >> 4) & 0xF; int index = baseIndex + (by * length + bz) * width + bx; - if( blocks[index] == 163 ) // custom block id + if( blocks[index] == customTile ) blocks[index] = chunk[i]; } } @@ -82,8 +84,8 @@ namespace ClassicalSharp.Map { 34, 35, 36, 22, 20, 49, 45, 1, 4, 0, 9, 11, 4, 19, 5, 17, 10, 49, 20, 1, 18, 12, 5, 25, 46, 44, 17, 49, 20, 1, 18, 12, 5, 25, 36, 34, 0, 9, 11, 46, 44, 0, 9, 11, 8, 10, 22, 27, 22, 8, 10, 28, 17, 49, 20, 1, 18, 12, 5, 25, 46, - 44, 11, 9, 0, 9, 11, 163, 0, 0, 9, 11, 0, 0, 0, 0, 0, 0, 0, 28, 22, 21, 11, - 0, 0, 0, 46, 46, 10, 10, 46, 20, 41, 42, 11, 9, 0, 8, 10, 10, 8, 0, 22, 22, + 44, 11, 9, 0, 9, 11, customTile, 0, 0, 9, 11, 0, 0, 0, 0, 0, 0, 0, 28, 22, 21, + 11, 0, 0, 0, 46, 46, 10, 10, 46, 20, 41, 42, 11, 9, 0, 8, 10, 10, 8, 0, 22, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 10, 0, 0, 0, 0, 0, 22, 22, 42, 3, 2, 29, 47, 0, 0, 0, 0, 0, 27, 46, 48, 24, 22, 36, 34, 8, 10, 21, 29, 22, 10, 22, 22, 41, 19, 35, 21, 29, 49, 34, 16, 41, 0, 22 }; diff --git a/ClassicalSharp/Network/NetworkProcessor.Original.cs b/ClassicalSharp/Network/NetworkProcessor.Original.cs index 7a54b2b77..8863657cf 100644 --- a/ClassicalSharp/Network/NetworkProcessor.Original.cs +++ b/ClassicalSharp/Network/NetworkProcessor.Original.cs @@ -57,8 +57,8 @@ namespace ClassicalSharp.Net { writer.WriteInt16( (short)(pos.X * 32) ); writer.WriteInt16( (short)((int)(pos.Y * 32) + 51) ); writer.WriteInt16( (short)(pos.Z * 32) ); - writer.WriteUInt8( (byte)Utils.DegreesToPacked( yaw, 256 ) ); - writer.WriteUInt8( (byte)Utils.DegreesToPacked( pitch, 256 ) ); + writer.WriteUInt8( (byte)Utils.DegreesToPacked( yaw ) ); + writer.WriteUInt8( (byte)Utils.DegreesToPacked( pitch ) ); } #endregion diff --git a/ClassicalSharp/Utils/Utils.cs b/ClassicalSharp/Utils/Utils.cs index 0ed3bf870..c69df0ca9 100644 --- a/ClassicalSharp/Utils/Utils.cs +++ b/ClassicalSharp/Utils/Utils.cs @@ -150,6 +150,10 @@ namespace ClassicalSharp { return (int)(degrees * period / 360.0) % period; } + public static int DegreesToPacked( double degrees ) { + return (int)(degrees * 256 / 360.0) & 0xFF; + } + public static double PackedToDegrees( byte packed ) { return packed * 360.0 / 256.0; }