diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj index 93eb1b6f4..31a9ed0e9 100644 --- a/ClassicalSharp/ClassicalSharp.csproj +++ b/ClassicalSharp/ClassicalSharp.csproj @@ -115,6 +115,7 @@ + diff --git a/ClassicalSharp/Map/MapFcm3.cs b/ClassicalSharp/Map/MapFcm3.cs new file mode 100644 index 000000000..f47ecdcae --- /dev/null +++ b/ClassicalSharp/Map/MapFcm3.cs @@ -0,0 +1,134 @@ +// 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; + +namespace ClassicalSharp { + + public sealed class MapFcm3 { + const uint Identifier = 0x0FC2AF40; + const byte Revision = 13; + + public static byte[] Load( Stream stream, Game game, out int width, out int height, out int length ) { + BinaryReader reader = new BinaryReader( stream ); + + if( reader.ReadInt32() != Identifier || reader.ReadByte() != Revision ) { + throw new InvalidDataException( "Unexpected constant in .fcm file" ); + } + + width = reader.ReadInt16(); + height = reader.ReadInt16(); + length = reader.ReadInt16(); + + int posX = reader.ReadInt32(); + int posY = reader.ReadInt32(); + int posZ = reader.ReadInt32(); + byte yaw = reader.ReadByte(); + byte pitch = reader.ReadByte(); + + reader.ReadUInt32(); // date modified + reader.ReadUInt32(); // date created + reader.ReadBytes( 16 ); // guid + reader.ReadBytes( 26 ); // layer index + int metaSize = reader.ReadInt32(); + + using( DeflateStream ds = new DeflateStream( stream, CompressionMode.Decompress ) ) { + reader = new BinaryReader( ds ); + for( int i = 0; i < metaSize; i++ ) { + string group = ReadString( reader ); + string key = ReadString( reader ); + string value = ReadString( reader ); + Console.WriteLine( group + "," + key + "," + value ); + } + byte[] blocks = new byte[width * height * length]; + int read = ds.Read( blocks, 0, blocks.Length ); + return blocks; + } + } + + public static void Save( Stream stream, Game game ) { + BinaryWriter writer = new BinaryWriter( stream ); + LocalPlayer p = game.LocalPlayer; + Map map = game.Map; + + 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.SpawnPoint.X * 32 ) ); + writer.Write( (int)( p.SpawnPoint.Y * 32 ) ); + writer.Write( (int)( p.SpawnPoint.Z * 32 ) ); + + writer.Write( (byte)0 ); // spawn R + writer.Write( (byte)0 ); // spawn L + + writer.Write( (uint)0 ); // date modified + writer.Write( (uint)0 ); // date created + + writer.Write( new byte[16] ); // map guid + writer.Write( (byte)1 ); // layer count + + long indexOffset = stream.Position; + writer.Seek( 25, SeekOrigin.Current ); + writer.Write( 9 ); // meta count + + int compressedLength; + long offset; + using( DeflateStream ds = new DeflateStream( stream, CompressionMode.Compress, true ) ) { + WriteMetadata( ds, game ); + offset = stream.Position; + + ds.Write( map.mapData, 0, map.mapData.Length ); + compressedLength = (int)( stream.Position - offset ); + } + + writer.BaseStream.Seek( indexOffset, SeekOrigin.Begin ); + writer.Write( (byte)0 ); + writer.Write( offset ); + writer.Write( compressedLength ); + writer.Write( 0 ); + writer.Write( 1 ); + writer.Write( map.mapData.Length ); + } + + static void WriteMetadata( Stream stream, Game game ) { + BinaryWriter writer = new BinaryWriter( stream ); + LocalPlayer p = game.LocalPlayer; + Map map = game.Map; + + WriteMetadataEntry( writer, "SkyCol", map.SkyCol.ToArgb() ); + WriteMetadataEntry( writer, "CloudsCol", map.CloudsCol.ToArgb() ); + WriteMetadataEntry( writer, "FogCol", map.FogCol.ToArgb() ); + + WriteMetadataEntry( writer, "ClickDist", (int)( p.ReachDistance * 32 ) ); + WriteMetadataEntry( writer, "SunLight", map.Sunlight.ToArgb() ); + WriteMetadataEntry( writer, "ShadowLight", map.Shadowlight.ToArgb() ); + + WriteMetadataEntry( writer, "Weather", (int)map.Weather ); + WriteMetadataEntry( writer, "SidesBlock", (int)map.SidesBlock ); + WriteMetadataEntry( writer, "EdgeBlock", (int)map.EdgeBlock ); + } + + static string ReadString( BinaryReader reader ) { + int length = reader.ReadUInt16(); + byte[] data = reader.ReadBytes( length ); + return Encoding.ASCII.GetString( data ); + } + + static void WriteString( BinaryWriter writer, string str ) { + byte[] data = Encoding.ASCII.GetBytes( str ); + writer.Write( (ushort)data.Length ); + writer.Write( data ); + } + + static void WriteMetadataEntry( BinaryWriter writer, string key, int value ) { + WriteString( writer, "CS_Client" ); + WriteString( writer, key ); + WriteString( writer, value.ToString() ); + } + } +} \ No newline at end of file diff --git a/ClassicalSharp/Singleplayer/Commands.cs b/ClassicalSharp/Singleplayer/Commands.cs index 98f7a8da8..baac3a7be 100644 --- a/ClassicalSharp/Singleplayer/Commands.cs +++ b/ClassicalSharp/Singleplayer/Commands.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using ClassicalSharp.Commands; namespace ClassicalSharp.Singleplayer { @@ -40,4 +41,57 @@ namespace ClassicalSharp.Singleplayer { } } } + + public sealed class LoadMapCommand : Command { + + public LoadMapCommand() { + Name = "LoadMap"; + Help = new [] { + "&a/client loadmap [filename]", + "&bfilename: &eLoads a fcm map from the specified filename.", + }; + } + + public override void Execute( CommandReader reader ) { + string path = reader.NextAll(); + if( String.IsNullOrEmpty( path ) ) return; + + path = Path.GetFileName( path ); + try { + using( FileStream fs = File.OpenRead( path ) ) { + int width, height, length; + game.Map.Reset(); + game.RaiseOnNewMap(); + game.SelectionManager.Dispose(); + + byte[] blocks = MapFcm3.Load( fs, game, out width, out height, out length ); + game.Map.UseRawMap( blocks, width, height, length ); + game.RaiseOnNewMapLoaded(); + } + } catch( FileNotFoundException ) { + game.AddChat( "&e/client load: Couldn't find file \"" + path + "\"" ); + } + } + } + + public sealed class SaveMapCommand : Command { + + public SaveMapCommand() { + Name = "SaveMap"; + Help = new [] { + "&a/client savemap [filename]", + "&bfilename: &eSpecifies name of the file to save the map as.", + }; + } + + public override void Execute( CommandReader reader ) { + string path = reader.NextAll(); + if( String.IsNullOrEmpty( path ) ) return; + + path = Path.GetFileName( path ); + using( FileStream fs = File.Create( path ) ) { + MapFcm3.Save( fs, game ); + } + } + } } diff --git a/ClassicalSharp/Singleplayer/Server.cs b/ClassicalSharp/Singleplayer/Server.cs index 8b7e7cd84..2ce796cb8 100644 --- a/ClassicalSharp/Singleplayer/Server.cs +++ b/ClassicalSharp/Singleplayer/Server.cs @@ -25,6 +25,8 @@ namespace ClassicalSharp.Singleplayer { NewMap(); MakeMap( 128, 128, 128 ); game.CommandManager.RegisterCommand( new GenerateCommand() ); + game.CommandManager.RegisterCommand( new SaveMapCommand() ); + game.CommandManager.RegisterCommand( new LoadMapCommand() ); } public override void SendChat( string text ) {