OSDN Git Service

Pre-PR5: Now with better, mostly-standard-compliant JSON
[automap/automap.git] / Automap / Subsystems / AutomapSystem.cs
index 7880ac2..f1f9cdc 100644 (file)
@@ -9,7 +9,7 @@ using System.Threading;
 
 using Hjg.Pngcs;
 using Hjg.Pngcs.Chunks;
-
+using Newtonsoft.Json;
 using Vintagestory.API.Client;
 using Vintagestory.API.Common;
 using Vintagestory.API.Config;
@@ -149,6 +149,7 @@ namespace Automap
                        {
                                uint ejectedItem = 0;
                                uint updatedChunks = 0;
+                               uint updatedPixels = 0;
 
                                //-- Should dodge enumerator changing underfoot....at a cost.
                                if (!columnCounter.IsEmpty)
@@ -167,13 +168,19 @@ namespace Automap
                                                        continue;
                                                }
 
-                                               ColumnMeta chunkMeta = CreateColumnMetadata(mostActiveCol, mapChunk);
-                                               PngWriter pngWriter = SetupPngImage(mostActiveCol.Key, chunkMeta);
-                                               UpdateEntityMetadata();
-                                               ProcessChunkBlocks(mostActiveCol.Key, mapChunk, chunkMeta);
+                                               ColumnMeta chunkMeta;
+                                               if (chunkTopMetadata.Contains(mostActiveCol.Key)) {
+                                               chunkMeta = chunkTopMetadata[mostActiveCol.Key];
+                                               }
+                                               else {
+                                               chunkMeta = CreateColumnMetadata(mostActiveCol, mapChunk);
+                                               }
 
+                                               UpdateEntityMetadata();
+                                               ProcessChunkBlocks(mostActiveCol.Key, mapChunk, ref chunkMeta);
 
-                                               ChunkRenderer.GenerateChunkPngShard(mostActiveCol.Key, mapChunk, chunkMeta, pngWriter, out uint updatedPixels);
+                                               PngWriter pngWriter = SetupPngImage(mostActiveCol.Key, chunkMeta);
+                                               ChunkRenderer.GenerateChunkPngShard(mostActiveCol.Key, mapChunk, chunkMeta, pngWriter, out updatedPixels);
 
                                                if (updatedPixels > 0)
                                                {
@@ -241,7 +248,7 @@ namespace Automap
 
                        this.BlockID_Designators = new Dictionary<int, BlockDesignator>();
                        this.Entity_Designators = new Dictionary<AssetLocation, EntityDesignator>();
-                       this.RockIdCodes = Helpers.ArbitrarytBlockIdHunter(ClientAPI, new AssetLocation(GlobalConstants.DefaultDomain, "rock"), EnumBlockMaterial.Stone);
+                       this.RockIdCodes = Helpers.ArbitrarytBlockIdHunter(ClientAPI, new AssetLocation(GlobalConstants.DefaultDomain, "rock-"), EnumBlockMaterial.Stone);
 
                        //Add special marker types for BlockID's of "Interest", overwrite colour, and method
 
@@ -283,6 +290,7 @@ namespace Automap
 
                }
 
+               //TODO: Rewrite as Newtonsoft  JsonTextWriter !!!
                /// <summary>
                /// Generates the JSON Metadata. (in Map object format )
                /// </summary>
@@ -290,74 +298,214 @@ namespace Automap
                {
                        string jsonFilename = Path.Combine(path, "Metadata.js");
 
-                       StreamWriter jsonWriter = new StreamWriter(jsonFilename, false, Encoding.UTF8);
+                       StreamWriter stream = new StreamWriter(jsonFilename, false, Encoding.UTF8);
+
+                       using (stream) {
+                       JsonTextWriter jsonWriter = new JsonTextWriter(stream);
+
+                       jsonWriter.Formatting = Formatting.None;
+                       jsonWriter.StringEscapeHandling = StringEscapeHandling.EscapeHtml;
+                       jsonWriter.Indentation = 0;
+                       //jsonWriter.AutoCompleteOnClose = true;
+                       jsonWriter.QuoteChar = '\'';
+                       jsonWriter.DateFormatHandling = DateFormatHandling.IsoDateFormat;
+                       jsonWriter.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
+
                        using (jsonWriter)
                        {
-                               jsonWriter.Write("ViewFrame.chunks={};");
-                               jsonWriter.Write("ViewFrame.chunks.worldSeedNum={0};", ClientAPI.World.Seed);
-                               jsonWriter.Write("ViewFrame.chunks.genTime=new Date('{0}');", DateTimeOffset.UtcNow.ToString("O"));
-                               jsonWriter.Write("ViewFrame.chunks.startCoords=[{0},{1}];", startChunkColumn.X, startChunkColumn.Y);
-                               jsonWriter.Write("ViewFrame.chunks.chunkSize={0};", chunkSize);
-                               jsonWriter.Write("ViewFrame.chunks.northMostChunk={0};", chunkTopMetadata.North_mostChunk);
-                               jsonWriter.Write("ViewFrame.chunks.southMostChunk={0};", chunkTopMetadata.South_mostChunk);
-                               jsonWriter.Write("ViewFrame.chunks.eastMostChunk={0};", chunkTopMetadata.East_mostChunk);
-                               jsonWriter.Write("ViewFrame.chunks.westMostChunk={0};", chunkTopMetadata.West_mostChunk);
+                               jsonWriter.WriteRaw("ViewFrame.chunks={};\n");
+                               jsonWriter.WriteRaw("ViewFrame.chunks.worldSeedNum=" );
+                               jsonWriter.WriteValue(ClientAPI.World.Seed);
+                               jsonWriter.WriteRaw(";\n");
+
+                               jsonWriter.WriteRaw("ViewFrame.chunks.genTime=");
+                               jsonWriter.WriteValue(DateTimeOffset.UtcNow);
+                               jsonWriter.WriteRaw(";\n");
+
+                               jsonWriter.WriteRaw("ViewFrame.chunks.startCoords=");
+                               jsonWriter.WriteStartArray( );
+                               jsonWriter.WriteValue(startChunkColumn.X);
+                               jsonWriter.WriteValue(startChunkColumn.Y);
+                               jsonWriter.WriteEndArray( );
+                               jsonWriter.WriteRaw(";\n");
+
+                               jsonWriter.WriteRaw("ViewFrame.chunks.chunkSize=");
+                               jsonWriter.WriteValue(chunkSize);
+                               jsonWriter.WriteRaw(";\n");
+
+                               jsonWriter.WriteRaw("ViewFrame.chunks.northMostChunk=");
+                               jsonWriter.WriteValue(chunkTopMetadata.North_mostChunk);
+                               jsonWriter.WriteRaw(";\n");
+
+                               jsonWriter.WriteRaw("ViewFrame.chunks.southMostChunk=");
+                               jsonWriter.WriteValue(chunkTopMetadata.South_mostChunk);
+                               jsonWriter.WriteRaw(";\n");
+
+                               jsonWriter.WriteRaw("ViewFrame.chunks.westMostChunk=");
+                               jsonWriter.WriteValue(chunkTopMetadata.West_mostChunk);
+                               jsonWriter.WriteRaw(";\n");
+
+                               jsonWriter.WriteRaw("ViewFrame.chunks.eastMostChunk=");
+                               jsonWriter.WriteValue(chunkTopMetadata.East_mostChunk);
+                               jsonWriter.WriteRaw(";\n");
+
+
                                //MAP object format - [key, value]: key is "x_y"
-                               jsonWriter.Write("ViewFrame.chunks.chunkMetadata=new Map([");
+                               jsonWriter.WriteRaw("ViewFrame.chunks.chunkMetadata=");
+                               jsonWriter.WriteStartConstructor("Map");
+                               jsonWriter.WriteStartArray( );//An array of... 2-component arrays
+
+
                                foreach (var shard in chunkTopMetadata)
                                {
-                                       jsonWriter.Write("['{0}_{1}',", shard.Location.X, shard.Location.Y);
-                                       jsonWriter.Write("{");
-                                       jsonWriter.Write("prettyCoord:'{0}',", shard.Location.PrettyCoords(ClientAPI));
-                                       jsonWriter.Write("chunkAge:'{0}',", shard.ChunkAge.ToString("g"));//World age - relative? or last edit ??
-                                       jsonWriter.Write("temp:'{0}',", shard.Temperature.ToString("F1"));
-                                       jsonWriter.Write("YMax:'{0}',", shard.YMax);
-                                       jsonWriter.Write("fert:'{0}',", shard.Fertility.ToString("F1"));
-                                       jsonWriter.Write("forestDens:'{0}',", shard.ForestDensity.ToString("F1"));
-                                       jsonWriter.Write("rain:'{0}',", shard.Rainfall.ToString("F1"));
-                                       jsonWriter.Write("shrubDens:'{0}',", shard.ShrubDensity.ToString("F1"));
-                                       jsonWriter.Write("airBlocks:'{0}',", shard.AirBlocks);
-                                       jsonWriter.Write("nonAirBlocks:'{0}',", shard.NonAirBlocks);
-                                       //TODO: Heightmap
-                                       //TODO: Rock-ratio
-                                       jsonWriter.Write("}],");
+                                       jsonWriter.WriteStartArray( );//Start tuple
+                                       jsonWriter.WriteValue($"{shard.Location.X}_{shard.Location.Y}");//Key of Tuple
+
+                                       jsonWriter.WriteStartObject( );
+                                       jsonWriter.WritePropertyName("prettyCoord");
+                                       jsonWriter.WriteValue( shard.Location.PrettyCoords(ClientAPI));
+
+                                       jsonWriter.WritePropertyName("chunkAge");
+                                       jsonWriter.WriteValue(shard.ChunkAge);
+
+                                       jsonWriter.WritePropertyName("temp");
+                                       jsonWriter.WriteValue(shard.Temperature);
+
+                                       jsonWriter.WritePropertyName("YMax");
+                                       jsonWriter.WriteValue(shard.YMax);
+
+                                       jsonWriter.WritePropertyName("fert");
+                                       jsonWriter.WriteValue(shard.Fertility);
+                                       
+                                       jsonWriter.WritePropertyName("forestDens");
+                       jsonWriter.WriteValue( shard.ForestDensity);
+
+                                       jsonWriter.WritePropertyName("rain"); 
+                       jsonWriter.WriteValue( shard.Rainfall);
+
+                                       jsonWriter.WritePropertyName("shrubDens");
+                       jsonWriter.WriteValue(  shard.ShrubDensity);
+
+                                       jsonWriter.WritePropertyName("airBlocks");
+                       jsonWriter.WriteValue( shard.AirBlocks);
+
+                                       jsonWriter.WritePropertyName("nonAirBlocks");
+                       jsonWriter.WriteValue(  shard.NonAirBlocks);
+
+                                       //TODO: Heightmap ?
+                                       //Start rockMap ; FOR a Ratio....on tooltip GUI
+                                       jsonWriter.WritePropertyName("rockRatio");
+                                       jsonWriter.WriteStartConstructor("Map");
+                                       jsonWriter.WriteStartArray( );
+                                       foreach (var rockEntry in shard.RockRatio) {
+                                               jsonWriter.WriteStartArray( );
+                                               jsonWriter.WriteValue(rockEntry.Key);//BlockID
+                                               jsonWriter.WriteValue(rockEntry.Value);//Total per chunk-column
+                                               jsonWriter.WriteEndArray( );
+                                       }
+                                       jsonWriter.WriteEndArray( );
+                                       jsonWriter.WriteEndConstructor( );//end rock-map
+
+                                       jsonWriter.WriteEndObject( );//end Map value: {Object}
+                                       jsonWriter.WriteEndArray( );//end Tuple
                                }
-                               jsonWriter.Write("]);");
 
+                               jsonWriter.WriteEndArray( );//Enclose tuples of chunkMetadata
+                               jsonWriter.WriteEndConstructor( );//Close constructor of Map (chunkMetadata)
+                               jsonWriter.WriteRaw(";\n");
+
+                               jsonWriter.WriteRaw("ViewFrame.chunks.pointsOfInterest=");
+                               jsonWriter.WriteStartConstructor("Map");
+                               jsonWriter.WriteStartArray( );//An array of... 2-component arrays
 
-                               jsonWriter.Write("ViewFrame.chunks.pointsOfInterest = new Map([");
                                foreach (var poi in POIs)
                                {
-                                       jsonWriter.Write("['{0}_{1}',", poi.Location.X, poi.Location.Z);
-                                       jsonWriter.Write("{");
-                                       jsonWriter.Write("prettyCoord:'{0}',", poi.Location.PrettyCoords(ClientAPI));
-                                       jsonWriter.Write("notes:'{0}',", poi.Notes.Replace("'", "\'").Replace("\n","\\n"));
-                                       jsonWriter.Write("time:new Date('{0}'),", poi.Timestamp.ToString("O"));
-                                       jsonWriter.Write("chunkPos:'{0}_{1}',", (poi.Location.X / chunkSize), (poi.Location.Z / chunkSize));
-                                       jsonWriter.Write("}],");
+                                       jsonWriter.WriteStartArray( );
+                                       jsonWriter.WriteValue($"{poi.Location.X}_{poi.Location.Z}");
+
+                                       jsonWriter.WriteStartObject();
+                                       jsonWriter.WritePropertyName("prettyCoord");
+                                       jsonWriter.WriteValue(poi.Location.PrettyCoords(ClientAPI) );
+
+                                       jsonWriter.WritePropertyName("notes");
+                                       jsonWriter.WriteValue(poi.Notes);//Encoded to HTML Entities
+
+                                       jsonWriter.WritePropertyName("time");
+                                       jsonWriter.WriteValue(poi.Timestamp);
+                                       
+                                       jsonWriter.WritePropertyName("chunkPos");
+                                       jsonWriter.WriteValue($"{(poi.Location.X / chunkSize)}_{(poi.Location.Z / chunkSize)}");
+                                       
+                                       jsonWriter.WriteEndObject( );
+                                       jsonWriter.WriteEndArray( );
                                }
 
                                foreach (var poi in EOIs)
                                {
-                                       jsonWriter.Write("['{0}_{1}',", poi.Location.X, poi.Location.Z);
-                                       jsonWriter.Write("{");
-                                       jsonWriter.Write("prettyCoord:'{0}',", poi.Location.PrettyCoords(ClientAPI));
-                                       jsonWriter.Write("notes:'{0}',", poi.Notes.Replace("'", "\'"));
-                                       jsonWriter.Write("time:new Date('{0}'),", poi.Timestamp.ToString("O"));
-                                       jsonWriter.Write("chunkPos:'{0}_{1}',", (poi.Location.X / chunkSize), (poi.Location.Z / chunkSize));
-                                       jsonWriter.Write("}],");
+                                       jsonWriter.WriteStartArray( );
+                                       jsonWriter.WriteValue($"{poi.Location.X}_{poi.Location.Z}");
+
+                                       jsonWriter.WriteStartObject( );
+                                       jsonWriter.WritePropertyName("prettyCoord");
+                                       jsonWriter.WriteValue(poi.Location.PrettyCoords(ClientAPI));
+
+                                       jsonWriter.WritePropertyName("notes");
+                                       jsonWriter.WriteValue(poi.Notes);//Encoded to HTML Entities
+
+                                       jsonWriter.WritePropertyName("time");
+                                       jsonWriter.WriteValue(poi.Timestamp);
+
+                                       jsonWriter.WritePropertyName("chunkPos");
+                                       jsonWriter.WriteValue($"{(poi.Location.X / chunkSize)}_{(poi.Location.Z / chunkSize)}");
+
+                                       jsonWriter.WriteEndObject( );
+                                       jsonWriter.WriteEndArray( );
+                               }
+
+                               jsonWriter.WriteEndArray( );
+                               jsonWriter.WriteEndConstructor( );
+                               jsonWriter.WriteRaw(";\n");
+
+                               jsonWriter.WriteWhitespace("\n");
+                               jsonWriter.WriteComment("============= BlockID's for Rockmap / Rock-ratios ===============");
+                               jsonWriter.WriteWhitespace("\n");
+
+                               jsonWriter.WriteRaw("var rock_Lookup =");
+                               jsonWriter.WriteStartConstructor("Map");
+                               jsonWriter.WriteStartArray( );//An array of... 2-component arrays
+
+                               foreach (var entry in RockIdCodes) {
+                               var block = ClientAPI.World.GetBlock(entry.Key);
+                               
+                               jsonWriter.WriteStartArray( );
+                               jsonWriter.WriteValue(entry.Key);//BlockID
+                                                                       
+                               jsonWriter.WriteStartObject( );
+                               jsonWriter.WritePropertyName("assetCode");
+                               jsonWriter.WriteValue(entry.Value);
+
+                               jsonWriter.WritePropertyName("name");
+                               jsonWriter.WriteValue(Lang.GetUnformatted(block.Code.Path));
+                               //Color?
+
+                               jsonWriter.WriteEndObject( );
+                               jsonWriter.WriteEndArray( );
                                }
-                               jsonWriter.Write("]);");
+                               jsonWriter.WriteEndArray( );
+                               jsonWriter.WriteEndConstructor();
+                               
+                               jsonWriter.WriteRaw(";\n");
 
                                jsonWriter.Flush();
                        }
+                       }
 
                }
 
 
                private ColumnMeta CreateColumnMetadata(KeyValuePair<Vec2i, uint> mostActiveCol, IMapChunk mapChunk)
                {
-                       ColumnMeta data = new ColumnMeta(mostActiveCol.Key.Copy(), chunkSize);
+                       ColumnMeta data = new ColumnMeta(mostActiveCol.Key.Copy(), ( byte )chunkSize);
                        BlockPos equivBP = new BlockPos(mostActiveCol.Key.X * chunkSize,
                                                                                        mapChunk.YMax,
                                                                                        mostActiveCol.Key.Y * chunkSize);
@@ -383,37 +531,43 @@ namespace Automap
 
                                if (files.Length > 0)
                                {
-#if DEBUG
+                                       #if DEBUG
                                        Logger.VerboseDebug("{0} Existing world chunk shards", files.Length);
-#endif
+                                       #endif
+                                        
+                                       foreach (var shardFile in files) {
 
+                                       if (shardFile.Length < 1024) continue;
+                                       var result = chunkShardRegex.Match(shardFile.Name);
+                                       if (result.Success) {
+                                       int X_chunk_pos = int.Parse(result.Groups["X"].Value);
+                                       int Z_chunk_pos = int.Parse(result.Groups["Z"].Value);
 
-
-                                       foreach (var shardFile in files)
-                                       {
-
-                                               if (shardFile.Length < 512) continue;
-                                               var result = chunkShardRegex.Match(shardFile.Name);
-                                               if (result.Success)
+                                       try 
                                                {
-                                                       int X_chunk_pos = int.Parse(result.Groups["X"].Value);
-                                                       int Z_chunk_pos = int.Parse(result.Groups["Z"].Value);
+                                               using (var fileStream = shardFile.OpenRead( )) {
 
-                                                       //Parse PNG chunks for METADATA in shard
-                                                       using var fileStream = shardFile.OpenRead();
-                                                       //TODO: Add corrupted PNG Exception handing HERE !
-                                                       PngReader pngRead = new PngReader(fileStream);
-                                                       pngRead.ReadSkippingAllRows();
-                                                       pngRead.End();
+                                               PngReader pngRead = new PngReader(fileStream);
+                                               pngRead.ReadSkippingAllRows( );
+                                               pngRead.End( );
+                                               //Parse PNG chunks for METADATA in shard
+                                               PngMetadataChunk metadataFromPng = pngRead.GetChunksList( ).GetById1(PngMetadataChunk.ID) as PngMetadataChunk;
 
-                                                       PngMetadataChunk metadataFromPng = pngRead.GetChunksList().GetById1(PngMetadataChunk.ID) as PngMetadataChunk;
-
-                                                       chunkTopMetadata.Add(metadataFromPng.ChunkMetadata);
+                                               chunkTopMetadata.Add(metadataFromPng.ChunkMetadata);
+                                               }
 
                                                }
+                                               catch (PngjException someEx) 
+                                               {
+                                                       Logger.Error("PNG Corruption file '{0}' - Reason: {1}", shardFile.Name, someEx);
+                                                       continue;
+                                               }
                                        }
 
+                                       }
                                }
+
+
                        }
                        else
                        {
@@ -444,36 +598,39 @@ namespace Automap
                                ChunkMetadata = metadata
                        };
                        pngWriter.GetChunksList().Queue(pngChunkMeta);
+                       pngWriter.CompLevel = 9;// 9 is the maximum compression
+                       pngWriter.CompressionStrategy = Hjg.Pngcs.Zlib.EDeflateCompressStrategy.Huffman;
 
                        return pngWriter;
                }
 
                /// <summary>
-               /// Does the heavy lifting of Scanning columns of chunks - creates Heightmap and Processes POIs, Entities, and stats...
+               /// Does the heavy lifting of Scanning columns of chunks - scans for BlockEntity, creates Heightmap and stats...
                /// </summary>
                /// <param name="key">Chunk Coordinate</param>
                /// <param name="mapChunk">Map chunk.</param>
                /// <param name="chunkMeta">Chunk metadata</param>
-               private void ProcessChunkBlocks(Vec2i key, IMapChunk mapChunk, ColumnMeta chunkMeta)
+               private void ProcessChunkBlocks(Vec2i key, IMapChunk mapChunk, ref ColumnMeta chunkMeta)
                {
 
                        int targetChunkY = mapChunk.YMax / chunkSize;//Surface ... 
                        for (; targetChunkY > 0; targetChunkY--)
                        {
-                               if (!(ClientAPI.World.BlockAccessor.GetChunk(key.X, targetChunkY, key.Y) is WorldChunk chunkData) || chunkData.BlockEntities == null)
-                               {
-#if DEBUG
-                                       Logger.VerboseDebug("Chunk null or empty X{0} Y{1} Z{2}", key.X, targetChunkY, key.Y);
-#endif
-                                       continue;
-                               }
+                       WorldChunk chunkData = ClientAPI.World.BlockAccessor.GetChunk(key.X, targetChunkY, key.Y) as WorldChunk;
+
+                       if (chunkData == null || chunkData.BlockEntities == null) {
+                       #if DEBUG
+                       Logger.VerboseDebug("Chunk null or empty X{0} Y{1} Z{2}", key.X, targetChunkY, key.Y);
+                       #endif
+                       continue;
+                       }
 
                                /*************** Chunk Entities Scanning *********************/
                                if (chunkData.BlockEntities != null && chunkData.BlockEntities.Length > 0)
                                {
-#if DEBUG
+                               #if DEBUG
                                        Logger.VerboseDebug("Surface@ {0} = BlockEntities: {1}", key, chunkData.BlockEntities.Length);
-#endif
+                               #endif
 
                                        foreach (var blockEnt in chunkData.BlockEntities)
                                        {