OSDN Git Service

Pre-PR: Point Metadata persistence, GUI work
[automap/automap.git] / Automap / Subsystems / AutomapSystem.cs
1 using System;
2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.IO;
5 using System.Linq;
6 using System.Text;
7 using System.Text.RegularExpressions;
8 using System.Threading;
9
10 using Hjg.Pngcs;
11 using Hjg.Pngcs.Chunks;
12
13 using Newtonsoft.Json;
14
15 using ProtoBuf;
16
17 using Vintagestory.API.Client;
18 using Vintagestory.API.Common;
19 using Vintagestory.API.Config;
20 using Vintagestory.API.Datastructures;
21 using Vintagestory.API.MathTools;
22 using Vintagestory.Common;
23
24 namespace Automap
25 {
26         public class AutomapSystem
27         {
28                 private Thread cartographer_thread;
29                 private ICoreClientAPI ClientAPI { get; set; }
30                 private ILogger Logger { get; set; }
31                 private IChunkRenderer ChunkRenderer { get; set; }
32
33                 private const string _mapPath = @"Maps";
34                 private const string _chunkPath = @"Chunks";
35                 private const string _domain = @"automap";
36                 private const string chunkFile_filter = @"*_*.png";
37                 private const string poiFileName = @"poi_binary";
38                 private const string eoiFileName = @"eoi_binary";
39                 private static Regex chunkShardRegex = new Regex(@"(?<X>[\d]+)_(?<Z>[\d]+).png", RegexOptions.Singleline);
40
41                 private ConcurrentDictionary<Vec2i, uint> columnCounter = new ConcurrentDictionary<Vec2i, uint>(3, 150);
42                 private ColumnsMetadata chunkTopMetadata;
43                 private PointsOfInterest POIs = new PointsOfInterest();
44                 private EntitiesOfInterest EOIs = new EntitiesOfInterest();
45
46                 internal Dictionary<int, BlockDesignator> BlockID_Designators { get; private set; }
47                 internal Dictionary<AssetLocation, EntityDesignator> Entity_Designators { get; private set; }
48                 internal Dictionary<int, string> RockIdCodes { get; private set; }
49
50                 internal CommandType CurrentState { get; set; }
51                 //Run status, Chunks processed, stats, center of map....
52                 private uint nullChunkCount, updatedChunksTotal;
53                 private Vec2i startChunkColumn;
54
55                 private readonly int chunkSize;
56                 private string path;
57                 private IAsset staticMap;
58                 private PersistedConfiguration configuration;
59
60
61                 public static string AutomapStatusEventKey = @"AutomapStatus";
62                 public static string AutomapCommandEventKey = @"AutomapCommand";
63                 PersistedConfiguration cachedConfiguration;
64
65                 public AutomapSystem(ICoreClientAPI clientAPI, ILogger logger, PersistedConfiguration config)
66                 {
67                         this.ClientAPI = clientAPI;
68                         this.Logger = logger;
69                         chunkSize = ClientAPI.World.BlockAccessor.ChunkSize;
70                         ClientAPI.Event.LevelFinalize += EngageAutomap;
71                         configuration = config;
72
73
74
75                         //TODO:Choose which one from GUI 
76                         this.ChunkRenderer = new StandardRenderer(clientAPI, logger);
77
78                         //Listen on bus for commands
79                         ClientAPI.Event.RegisterEventBusListener(CommandListener, 1.0, AutomapSystem.AutomapCommandEventKey);
80
81                         if (configuration.Autostart) 
82                         {
83                                 CurrentState = CommandType.Run;
84                                 Logger.Debug("Autostart is Enabled.");
85                         }
86
87                 }
88
89
90                 #region Internals
91                 private void EngageAutomap()
92                 {
93                         path = ClientAPI.GetOrCreateDataPath(_mapPath);
94                         path = ClientAPI.GetOrCreateDataPath(Path.Combine(path, "World_" + ClientAPI.World.Seed));//Add name of World too...'ServerApi.WorldManager.CurrentWorldName'
95
96
97                         string mapFilename = Path.Combine(path, "automap.html");
98                         StreamWriter outputText = new StreamWriter(File.Open(mapFilename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite));
99
100                         staticMap = ClientAPI.World.AssetManager.Get(new AssetLocation(_domain, "config/automap.html"));
101                         outputText.Write(staticMap.ToText());
102                         outputText.Flush();
103
104                         Prefill_POI_Designators();
105                         startChunkColumn = new Vec2i((ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.X / chunkSize), (ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Z / chunkSize));
106                         chunkTopMetadata = new ColumnsMetadata(startChunkColumn);
107
108                         Logger.Notification("AUTOMAP Start {0}", startChunkColumn);
109                         Reload_Metadata();
110
111                         ClientAPI.Event.ChunkDirty += ChunkAChanging;
112
113                         cartographer_thread = new Thread(Cartographer)
114                         {
115                                 Name = "Cartographer",
116                                 Priority = ThreadPriority.Lowest,
117                                 IsBackground = true
118                         };
119
120                         ClientAPI.Event.RegisterGameTickListener(AwakenCartographer, 6000);
121                 }
122
123                 private void ChunkAChanging(Vec3i chunkCoord, IWorldChunk chunk, EnumChunkDirtyReason reason)
124                 {
125                         Vec2i topPosition = new Vec2i(chunkCoord.X, chunkCoord.Z);
126
127                         columnCounter.AddOrUpdate(topPosition, 1, (key, colAct) => colAct + 1);
128                 }
129
130                 private void AwakenCartographer(float delayed)
131                 {
132
133                         if (CurrentState == CommandType.Run && (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true))
134                         {
135                                 #if DEBUG
136                                 Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState);
137                                 #endif
138
139                                 if (cartographer_thread.ThreadState.HasFlag(ThreadState.Unstarted))
140                                 {
141                                         cartographer_thread.Start();
142                                 }
143                                 else if (cartographer_thread.ThreadState.HasFlag(ThreadState.WaitSleepJoin))
144                                 {
145                                         //Time to (re)write chunk shards
146                                         cartographer_thread.Interrupt();
147                                 }
148                                 //#if DEBUG
149                                 //ClientAPI.TriggerChatMessage($"Automap {updatedChunksTotal} Updates - MAX (N:{chunkTopMetadata.North_mostChunk},S:{chunkTopMetadata.South_mostChunk},E:{chunkTopMetadata.East_mostChunk}, W:{chunkTopMetadata.West_mostChunk} - TOTAL: {chunkTopMetadata.Count})");
150                                 //#endif
151                         }
152                         else if (CurrentState == CommandType.Snapshot)
153                         {
154                                 //TODO: Snapshot generator second thread...
155                         }
156
157                 }
158
159
160                 private void Cartographer()
161                 {
162                         wake:
163                         Logger.VerboseDebug("Cartographer thread awoken");
164
165                         try
166                         {
167                                 uint ejectedItem = 0;
168                                 uint updatedChunks = 0;
169                                 uint updatedPixels = 0;
170
171                                 //-- Should dodge enumerator changing underfoot....at a cost.
172                                 if (!columnCounter.IsEmpty)
173                                 {
174                                         var tempSet = columnCounter.ToArray().OrderByDescending(kvp => kvp.Value);
175                                         UpdateEntityMetadata( );
176
177                                         foreach (var mostActiveCol in tempSet)
178                                         {
179                                                 var mapChunk = ClientAPI.World.BlockAccessor.GetMapChunk(mostActiveCol.Key);
180
181                                                 if (mapChunk == null)
182                                                 {
183                                                         Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!", mostActiveCol.Key);
184                                                         nullChunkCount++;
185                                                         columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
186                                                         continue;
187                                                 }
188
189                                                 ColumnMeta chunkMeta;
190                                                 if (chunkTopMetadata.Contains(mostActiveCol.Key)) {
191                                                 chunkMeta = chunkTopMetadata[mostActiveCol.Key];
192                                                 }
193                                                 else {
194                                                 chunkMeta = CreateColumnMetadata(mostActiveCol, mapChunk);
195                                                 }
196
197                                                 ProcessChunkBlocks(mostActiveCol.Key, mapChunk, ref chunkMeta);
198
199                                                 PngWriter pngWriter = SetupPngImage(mostActiveCol.Key, chunkMeta);
200                                                 ChunkRenderer.GenerateChunkPngShard(mostActiveCol.Key, mapChunk, chunkMeta, pngWriter, out updatedPixels);
201
202                                                 if (updatedPixels > 0)
203                                                 {
204
205                                                         #if DEBUG
206                                                         Logger.VerboseDebug("Wrote chunk shard: ({0}) - Edits#:{1}, Pixels#:{2}", mostActiveCol.Key, mostActiveCol.Value, updatedPixels);
207                                                         #endif
208                                                         updatedChunks++;
209                                                         chunkTopMetadata.Update(chunkMeta);
210                                                         columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
211                                                 }
212                                                 else
213                                                 {
214                                                         columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
215                                                         Logger.VerboseDebug("Un-painted chunk: ({0}) ", mostActiveCol.Key);
216                                                 }
217
218                                         }
219                                 }
220
221                                 UpdateStatus(this.updatedChunksTotal, this.nullChunkCount, updatedChunks);
222
223                                 if (updatedChunks > 0)
224                                 {
225                                         //What about chunk updates themselves; a update bitmap isn't kept...
226                                         updatedChunksTotal += updatedChunks;
227                                         GenerateJSONMetadata();
228                                         PersistPointsData( );
229                                         updatedChunks = 0;
230                                 }
231
232                                 //Then sleep until interupted again, and repeat
233
234                                 Logger.VerboseDebug("Thread '{0}' about to sleep indefinitely.", Thread.CurrentThread.Name);
235
236                                 Thread.Sleep(Timeout.Infinite);
237
238                         }
239                         catch (ThreadInterruptedException)
240                         {
241
242                                 Logger.VerboseDebug("Thread '{0}' interupted [awoken]", Thread.CurrentThread.Name);
243                                 goto wake;
244
245                         }
246                         catch (ThreadAbortException)
247                         {
248                                 Logger.VerboseDebug("Thread '{0}' aborted.", Thread.CurrentThread.Name);
249
250                         }
251                         finally
252                         {
253                                 Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name);
254                         }
255                 }
256
257                 private void UpdateStatus(uint totalUpdates, uint voidChunks, uint delta)
258                 {
259                         StatusData updateData = new StatusData(totalUpdates, voidChunks, delta, CommandType.Run);
260
261                         this.ClientAPI.Event.PushEvent(AutomapStatusEventKey, updateData);
262                 }
263
264                 private void Prefill_POI_Designators()
265                 {
266
267                         this.BlockID_Designators = new Dictionary<int, BlockDesignator>();
268                         this.Entity_Designators = new Dictionary<AssetLocation, EntityDesignator>();
269                         this.RockIdCodes = Helpers.ArbitrarytBlockIdHunter(ClientAPI, new AssetLocation(GlobalConstants.DefaultDomain, "rock-"), EnumBlockMaterial.Stone);
270
271                         //Add special marker types for BlockID's of "Interest", overwrite colour, and method
272
273                         Reload_POI_Designators();
274                 }
275
276                 private void Reload_POI_Designators()
277                 {                       
278                         Logger.VerboseDebug("Connecting {0} Configured Block-Designators", configuration.BlockDesignators.Count);
279                         foreach (var designator in configuration.BlockDesignators)
280                         {
281                                 var blockIDs = Helpers.ArbitrarytBlockIdHunter(ClientAPI, designator.Pattern, designator.Material);
282                                 if (blockIDs.Count > 0) { Logger.VerboseDebug("Designator {0} has {1} associated blockIDs", designator.ToString(), blockIDs.Count); }
283                                 foreach (var entry in blockIDs)
284                                 {
285                                         BlockID_Designators.Add(entry.Key, designator);
286                                 }
287                         }
288                         this.ChunkRenderer.BlockID_Designators = BlockID_Designators;
289
290
291                         Logger.VerboseDebug("Connecting {0} Configured Entity-Designators", configuration.EntityDesignators.Count);
292                         foreach (var designator in configuration.EntityDesignators)
293                         {
294                                 //Get Variants first, from EntityTypes...better be populated!
295                                 var matched = ClientAPI.World.EntityTypes.FindAll(entp => entp.Code.BeginsWith(designator.Pattern.Domain, designator.Pattern.Path));
296
297                                 foreach (var match in matched)
298                                 {
299                                         Logger.VerboseDebug("Linked Entity: {0} Designator: {1}", match.Code, designator);
300                                         this.Entity_Designators.Add(match.Code, designator);
301                                 }
302
303
304
305                                 //EntityProperties props = ClientAPI.World.GetEntityType(designator.Pattern);
306                         }
307
308
309                 }
310
311
312                 /// <summary>
313                 /// Generates the JSON Metadata. (in Map object format )
314                 /// </summary>
315                 private void GenerateJSONMetadata()
316                 {
317                         string jsonFilename = Path.Combine(path, "Metadata.js");
318
319                         StreamWriter stream = new StreamWriter(jsonFilename, false, Encoding.UTF8);
320
321                         using (stream) {
322                         JsonTextWriter jsonWriter = new JsonTextWriter(stream);
323
324                         jsonWriter.Formatting = Formatting.None;
325                         jsonWriter.StringEscapeHandling = StringEscapeHandling.EscapeHtml;
326                         jsonWriter.Indentation = 0;
327                         //jsonWriter.AutoCompleteOnClose = true;
328                         jsonWriter.QuoteChar = '\'';
329                         jsonWriter.DateFormatHandling = DateFormatHandling.IsoDateFormat;
330                         jsonWriter.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
331
332                         using (jsonWriter)
333                         {
334                                 jsonWriter.WriteRaw("ViewFrame.chunks={};\n");
335                                 jsonWriter.WriteRaw("ViewFrame.chunks.worldSeedNum=" );
336                                 jsonWriter.WriteValue(ClientAPI.World.Seed);
337                                 jsonWriter.WriteRaw(";\n");
338
339                                 jsonWriter.WriteRaw("ViewFrame.chunks.genTime=");
340                                 jsonWriter.WriteValue(DateTimeOffset.UtcNow);
341                                 jsonWriter.WriteRaw(";\n");
342
343                                 jsonWriter.WriteRaw("ViewFrame.chunks.startCoords=");
344                                 jsonWriter.WriteStartArray( );
345                                 jsonWriter.WriteValue(startChunkColumn.X);
346                                 jsonWriter.WriteValue(startChunkColumn.Y);
347                                 jsonWriter.WriteEndArray( );
348                                 jsonWriter.WriteRaw(";\n");
349
350                                 jsonWriter.WriteRaw("ViewFrame.chunks.chunkSize=");
351                                 jsonWriter.WriteValue(chunkSize);
352                                 jsonWriter.WriteRaw(";\n");
353
354                                 jsonWriter.WriteRaw("ViewFrame.chunks.northMostChunk=");
355                                 jsonWriter.WriteValue(chunkTopMetadata.North_mostChunk);
356                                 jsonWriter.WriteRaw(";\n");
357
358                                 jsonWriter.WriteRaw("ViewFrame.chunks.southMostChunk=");
359                                 jsonWriter.WriteValue(chunkTopMetadata.South_mostChunk);
360                                 jsonWriter.WriteRaw(";\n");
361
362                                 jsonWriter.WriteRaw("ViewFrame.chunks.westMostChunk=");
363                                 jsonWriter.WriteValue(chunkTopMetadata.West_mostChunk);
364                                 jsonWriter.WriteRaw(";\n");
365
366                                 jsonWriter.WriteRaw("ViewFrame.chunks.eastMostChunk=");
367                                 jsonWriter.WriteValue(chunkTopMetadata.East_mostChunk);
368                                 jsonWriter.WriteRaw(";\n");
369
370
371                                 //MAP object format - [key, value]: key is "x_y"
372                                 jsonWriter.WriteRaw("ViewFrame.chunks.chunkMetadata=");
373                                 jsonWriter.WriteStartConstructor("Map");
374                                 jsonWriter.WriteStartArray( );//An array of... 2-component arrays
375
376
377                                 foreach (var shard in chunkTopMetadata)
378                                 {
379                                         jsonWriter.WriteStartArray( );//Start tuple
380                                         jsonWriter.WriteValue($"{shard.Location.X}_{shard.Location.Y}");//Key of Tuple
381
382                                         jsonWriter.WriteStartObject( );
383                                         jsonWriter.WritePropertyName("prettyCoord");
384                                         jsonWriter.WriteValue( shard.Location.PrettyCoords(ClientAPI));
385
386                                         jsonWriter.WritePropertyName("chunkAge");
387                                         jsonWriter.WriteValue(shard.ChunkAge);
388
389                                         jsonWriter.WritePropertyName("temp");
390                                         jsonWriter.WriteValue(shard.Temperature);
391
392                                         jsonWriter.WritePropertyName("YMax");
393                                         jsonWriter.WriteValue(shard.YMax);
394
395                                         jsonWriter.WritePropertyName("fert");
396                                         jsonWriter.WriteValue(shard.Fertility);
397                                         
398                                         jsonWriter.WritePropertyName("forestDens");
399                         jsonWriter.WriteValue( shard.ForestDensity);
400
401                                         jsonWriter.WritePropertyName("rain"); 
402                         jsonWriter.WriteValue( shard.Rainfall);
403
404                                         jsonWriter.WritePropertyName("shrubDens");
405                         jsonWriter.WriteValue(  shard.ShrubDensity);
406
407                                         jsonWriter.WritePropertyName("airBlocks");
408                         jsonWriter.WriteValue( shard.AirBlocks);
409
410                                         jsonWriter.WritePropertyName("nonAirBlocks");
411                         jsonWriter.WriteValue(  shard.NonAirBlocks);
412
413                                         //TODO: Heightmap ?
414                                         //Start rockMap ; FOR a Ratio....on tooltip GUI
415                                         jsonWriter.WritePropertyName("rockRatio");
416                                         jsonWriter.WriteStartConstructor("Map");
417                                         jsonWriter.WriteStartArray( );
418                                         foreach (var rockEntry in shard.RockRatio) {
419                                                 var rockBlock = ClientAPI.World.GetBlock(rockEntry.Key);
420                                                 jsonWriter.WriteStartArray( );
421                                                 jsonWriter.WriteValue(rockBlock.Code.Path);
422                                                 jsonWriter.WriteValue(rockEntry.Value);//Total per chunk-column
423                                                 jsonWriter.WriteEndArray( );
424                                         }
425                                         jsonWriter.WriteEndArray( );
426                                         jsonWriter.WriteEndConstructor( );//end rock-map
427
428                                         jsonWriter.WriteEndObject( );//end Map value: {Object}
429                                         jsonWriter.WriteEndArray( );//end Tuple
430                                 }
431
432                                 jsonWriter.WriteEndArray( );//Enclose tuples of chunkMetadata
433                                 jsonWriter.WriteEndConstructor( );//Close constructor of Map (chunkMetadata)
434                                 jsonWriter.WriteRaw(";\n");
435
436                                 jsonWriter.WriteRaw("ViewFrame.chunks.pointsOfInterest=");
437                                 jsonWriter.WriteStartConstructor("Map");
438                                 jsonWriter.WriteStartArray( );//An array of... 2-component arrays
439
440                                 foreach (var poi in POIs)
441                                 {
442                                         jsonWriter.WriteStartArray( );
443                                         jsonWriter.WriteValue($"{poi.Location.X}_{poi.Location.Z}");
444
445                                         jsonWriter.WriteStartObject();
446                                         jsonWriter.WritePropertyName("Name");
447                                         jsonWriter.WriteValue(poi.Name);
448                                         
449                                         jsonWriter.WritePropertyName("prettyCoord");
450                                         jsonWriter.WriteValue(poi.Location.PrettyCoords(ClientAPI) );
451
452                                         jsonWriter.WritePropertyName("notes");
453                                         jsonWriter.WriteValue(poi.Notes);//Encoded to HTML Entities
454
455                                         jsonWriter.WritePropertyName("time");
456                                         jsonWriter.WriteValue(poi.Timestamp);
457                                         
458                                         jsonWriter.WritePropertyName("chunkPos");
459                                         jsonWriter.WriteValue($"{(poi.Location.X / chunkSize)}_{(poi.Location.Z / chunkSize)}");
460                                         
461                                         jsonWriter.WriteEndObject( );
462                                         jsonWriter.WriteEndArray( );
463                                 }
464
465                                 foreach (var poi in EOIs)
466                                 {
467                                         jsonWriter.WriteStartArray( );
468                                         jsonWriter.WriteValue($"{poi.Location.X}_{poi.Location.Z}");
469
470                                         jsonWriter.WriteStartObject( );
471                                         jsonWriter.WritePropertyName("Name");
472                                         jsonWriter.WriteValue(poi.Name);
473
474                                         jsonWriter.WritePropertyName("prettyCoord");
475                                         jsonWriter.WriteValue(poi.Location.PrettyCoords(ClientAPI));
476
477                                         jsonWriter.WritePropertyName("notes");
478                                         jsonWriter.WriteValue(poi.Notes);//Encoded to HTML Entities
479
480                                         jsonWriter.WritePropertyName("time");
481                                         jsonWriter.WriteValue(poi.Timestamp);
482
483                                         jsonWriter.WritePropertyName("chunkPos");
484                                         jsonWriter.WriteValue($"{(poi.Location.X / chunkSize)}_{(poi.Location.Z / chunkSize)}");
485
486                                         jsonWriter.WriteEndObject( );
487                                         jsonWriter.WriteEndArray( );
488                                 }
489
490                                 jsonWriter.WriteEndArray( );
491                                 jsonWriter.WriteEndConstructor( );
492                                 jsonWriter.WriteRaw(";\n");
493
494                                 jsonWriter.WriteWhitespace("\n");
495                                 jsonWriter.WriteComment("============= BlockID's for Rockmap / Rock-ratios ===============");
496                                 jsonWriter.WriteWhitespace("\n");
497
498                                 jsonWriter.WriteRaw("ViewFrame.chunks.rock_Lookup =");
499                                 jsonWriter.WriteStartConstructor("Map");
500                                 jsonWriter.WriteStartArray( );//An array of... 2-component arrays
501
502                                 foreach (var entry in RockIdCodes) {
503                                 var block = ClientAPI.World.GetBlock(entry.Key);
504                                 
505                                 jsonWriter.WriteStartArray( );
506                                 jsonWriter.WriteValue(block.Code.Path);
507                                                                         
508                                 jsonWriter.WriteStartObject( );
509                                 jsonWriter.WritePropertyName("assetCode");
510                                 jsonWriter.WriteValue(entry.Value);
511
512                                 jsonWriter.WritePropertyName("name");
513                                 jsonWriter.WriteValue(Lang.GetUnformatted(block.Code.Path));
514                                 //Color?
515
516                                 jsonWriter.WriteEndObject( );
517                                 jsonWriter.WriteEndArray( );
518                                 }
519                                 jsonWriter.WriteEndArray( );
520                                 jsonWriter.WriteEndConstructor();
521                                 
522                                 jsonWriter.WriteRaw(";\n");
523
524                                 jsonWriter.Flush();
525                         }
526                         }
527
528                 }
529
530                 /// <summary>
531                 /// Store Points/Entity of Interest
532                 /// </summary>
533                 private void PersistPointsData( )
534                 {
535                 //POI and EOI raw dump files ~ reload em!
536                 //var poiRawFile = File.
537                 string poiPath = Path.Combine(path, poiFileName);
538                 string eoiPath = Path.Combine(path, eoiFileName);
539
540                 if (this.POIs.Count > 0) {
541                 using (var poiFile = File.OpenWrite(poiPath)) {
542                         Serializer.Serialize<PointsOfInterest>(poiFile, this.POIs);
543                 }
544                 }
545
546                 if (this.EOIs.Count > 0) {
547                 using (var eoiFile = File.OpenWrite(eoiPath)) {
548                         Serializer.Serialize<EntitiesOfInterest>(eoiFile, this.EOIs);
549                 }
550                 }
551                 }
552
553                 private ColumnMeta CreateColumnMetadata(KeyValuePair<Vec2i, uint> mostActiveCol, IMapChunk mapChunk)
554                 {
555                         ColumnMeta data = new ColumnMeta(mostActiveCol.Key.Copy(), ( byte )chunkSize);
556                         BlockPos equivBP = new BlockPos(mostActiveCol.Key.X * chunkSize,
557                                                                                         mapChunk.YMax,
558                                                                                         mostActiveCol.Key.Y * chunkSize);
559
560                         var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP);
561                         data.UpdateFieldsFrom(climate, mapChunk, TimeSpan.FromHours(ClientAPI.World.Calendar.TotalHours));
562
563                         return data;
564                 }
565
566                 /// <summary>
567                 /// Reload chunk bounds from chunk shards
568                 /// </summary>
569                 /// <returns>The metadata.</returns>
570                 private void Reload_Metadata()
571                 {
572                         var worldmapDir = new DirectoryInfo(path);
573
574                         if (worldmapDir.Exists)
575                         {
576
577                                 var shardFiles = worldmapDir.GetFiles(chunkFile_filter);
578
579                                 if (shardFiles.Length > 0)
580                                 {
581                                         #if DEBUG
582                                         Logger.VerboseDebug("Metadata reloading from {0} shards", shardFiles.Length);
583                                         #endif
584                                          
585                                         foreach (var shardFile in shardFiles) {
586
587                                         if (shardFile.Length < 1024) continue;
588                                         var result = chunkShardRegex.Match(shardFile.Name);
589                                         if (result.Success) {
590                                         int X_chunk_pos = int.Parse(result.Groups["X"].Value);
591                                         int Z_chunk_pos = int.Parse(result.Groups["Z"].Value);
592
593                                         try 
594                                                 {
595                                                 using (var fileStream = shardFile.OpenRead( )) {
596
597                                                 PngReader pngRead = new PngReader(fileStream);
598                                                 pngRead.ReadSkippingAllRows( );
599                                                 pngRead.End( );
600                                                 //Parse PNG chunks for METADATA in shard
601                                                 PngMetadataChunk metadataFromPng = pngRead.GetChunksList( ).GetById1(PngMetadataChunk.ID) as PngMetadataChunk;
602
603                                                 chunkTopMetadata.Add(metadataFromPng.ChunkMetadata);
604                                                 }
605
606                                                 }
607                                                 catch (PngjException someEx) 
608                                                 {
609                                                         Logger.Error("PNG Corruption in file '{0}' - Reason: {1}", shardFile.Name, someEx);
610                                                         continue;
611                                                 }
612                                         }
613
614                                         }
615                                 }
616
617                                 //POI and EOI raw dump files ~ reload em!
618                                 //var poiRawFile = File.
619                                 string poiPath = Path.Combine(path, poiFileName);
620                                 string eoiPath = Path.Combine(path, eoiFileName);
621
622                                 if (File.Exists(poiPath)) {
623                                         using (var poiFile = File.OpenRead(poiPath)) {
624                                         this.POIs = Serializer.Deserialize<PointsOfInterest>(poiFile);
625                                         Logger.VerboseDebug("Reloaded {0} POIs from file.", this.POIs.Count);
626                                         }
627                                 }
628
629                                 if (File.Exists(eoiPath)) {
630                                         using (var eoiFile = File.OpenRead(eoiPath)) {
631                                         this.EOIs = Serializer.Deserialize<EntitiesOfInterest>(eoiFile);
632                                         Logger.VerboseDebug("Reloaded {0} EOIs from file.", this.EOIs.Count);
633                                         }
634                                 }
635
636                         }
637                         else
638                         {
639                                 #if DEBUG
640                                 Logger.VerboseDebug("Could not open world map directory");
641                                 #endif
642                         }
643
644
645
646                 }
647
648                 private PngWriter SetupPngImage(Vec2i coord, ColumnMeta metadata)
649                 {
650                         ImageInfo imageInf = new ImageInfo(chunkSize, chunkSize, 8, false);
651
652                         string filename = $"{coord.X}_{coord.Y}.png";
653                         filename = Path.Combine(path, filename);
654
655                         PngWriter pngWriter = FileHelper.CreatePngWriter(filename, imageInf, true);
656                         PngMetadata meta = pngWriter.GetMetadata();
657                         meta.SetTimeNow();
658                         meta.SetText("Chunk_X", coord.X.ToString("D"));
659                         meta.SetText("Chunk_Y", coord.Y.ToString("D"));
660                         //Setup specialized meta-data PNG chunks here...
661                         PngMetadataChunk pngChunkMeta = new PngMetadataChunk(pngWriter.ImgInfo)
662                         {
663                                 ChunkMetadata = metadata
664                         };
665                         pngWriter.GetChunksList().Queue(pngChunkMeta);
666                         pngWriter.CompLevel = 9;// 9 is the maximum compression
667                         pngWriter.CompressionStrategy = Hjg.Pngcs.Zlib.EDeflateCompressStrategy.Huffman;
668
669                         return pngWriter;
670                 }
671
672                 /// <summary>
673                 /// Does the heavy lifting of Scanning columns of chunks - scans for BlockEntity, creates Heightmap and stats...
674                 /// </summary>
675                 /// <param name="key">Chunk Coordinate</param>
676                 /// <param name="mapChunk">Map chunk.</param>
677                 /// <param name="chunkMeta">Chunk metadata</param>
678                 private void ProcessChunkBlocks(Vec2i key, IMapChunk mapChunk, ref ColumnMeta chunkMeta)
679                 {
680
681                         int targetChunkY = mapChunk.YMax / chunkSize;//Surface ... 
682                         for (; targetChunkY > 0; targetChunkY--)
683                         {
684                         WorldChunk chunkData = ClientAPI.World.BlockAccessor.GetChunk(key.X, targetChunkY, key.Y) as WorldChunk;
685
686                         if (chunkData == null || chunkData.BlockEntities == null) {
687                         #if DEBUG
688                         Logger.VerboseDebug("Chunk null or empty X{0} Y{1} Z{2}", key.X, targetChunkY, key.Y);
689                         #endif
690                         continue;
691                         }
692
693                                 /*************** Chunk Entities Scanning *********************/
694                                 if (chunkData.BlockEntities != null && chunkData.BlockEntities.Length > 0)
695                                 {
696                                 #if DEBUG
697                                         Logger.VerboseDebug("Surface@ {0} = BlockEntities: {1}", key, chunkData.BlockEntities.Length);
698                                 #endif
699
700                                         foreach (var blockEnt in chunkData.BlockEntities)
701                                         {
702
703                                                 if (blockEnt != null && blockEnt.Block != null && BlockID_Designators.ContainsKey(blockEnt.Block.BlockId))
704                                                 {
705                                                         var designator = BlockID_Designators[blockEnt.Block.BlockId];
706                                                         designator.SpecialAction(ClientAPI, POIs, blockEnt.Pos.Copy(), blockEnt.Block);
707                                                 }
708                                         }
709
710                                 }
711                                 /********************* Chunk/Column BLOCKs scanning ****************/
712                                 //Heightmap, Stats, block tally
713                                 chunkData.Unpack();
714
715                                 int X_index, Y_index, Z_index;
716                                 X_index = Y_index = Z_index = 0;
717
718                                 do
719                                 {
720                                         do
721                                         {
722                                                 do
723                                                 {
724                                                         /* Encode packed indicie
725                                                         (y * chunksize + z) * chunksize + x
726                                                         */
727                                                         var indicie = Helpers.ChunkBlockIndicie16(X_index, Y_index, Z_index);
728                                                         int aBlockId = chunkData.Blocks[indicie];
729
730                                                         if (aBlockId == 0)
731                                                         {//Air
732                                                                 chunkMeta.AirBlocks++;
733                                                                 continue;
734                                                         }
735
736                                                         if (RockIdCodes.ContainsKey(aBlockId))
737                                                         {
738                                                                 if (chunkMeta.RockRatio.ContainsKey(aBlockId)) { chunkMeta.RockRatio[aBlockId]++; } else { chunkMeta.RockRatio.Add(aBlockId, 1); }
739                                                         }
740
741                                                         chunkMeta.NonAirBlocks++;
742
743                                                         //Heightmap 
744                                                         if (chunkMeta.HeightMap[X_index, Z_index] == 0)
745                                                         { chunkMeta.HeightMap[X_index, Z_index] = (ushort) (Y_index + (targetChunkY * chunkSize)); }
746
747                                                 }
748                                                 while (X_index++ < (chunkSize - 1));
749                                                 X_index = 0;
750                                         }
751                                         while (Z_index++ < (chunkSize - 1));
752                                         Z_index = 0;
753                                 }
754                                 while (Y_index++ < (chunkSize - 1));
755
756                         }
757                 }
758
759                 private void UpdateEntityMetadata()
760                 {
761                         Logger.Debug("Presently {0} Entities", ClientAPI.World.LoadedEntities.Count);
762                         //Mabey scan only for 'new' entities by tracking ID in set?
763                         foreach (var loadedEntity in ClientAPI.World.LoadedEntities.ToArray())
764                         {
765
766                                 #if DEBUG
767                                 //Logger.VerboseDebug($"ENTITY: ({loadedEntity.Value.Code}) = #{loadedEntity.Value.EntityId} {loadedEntity.Value.State} {loadedEntity.Value.LocalPos}    <<<<<<<<<<<<");
768                                 #endif
769
770                                 var dMatch = Entity_Designators.SingleOrDefault(se => se.Key.Equals(loadedEntity.Value.Code));
771                                 if (dMatch.Value != null)
772                                 {
773                                         dMatch.Value.SpecialAction(ClientAPI, this.EOIs, loadedEntity.Value.LocalPos.AsBlockPos.Copy(), loadedEntity.Value);
774                                 }
775
776                         }
777
778
779                 }
780
781                 private void AddNote(string notation)
782                 {
783                         var playerNodePoi = new PointOfInterest()
784                         {
785                                 Name = "Note",
786                                 Location = ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Copy(),
787                                 Notes = notation,
788                                 Timestamp = DateTime.UtcNow,
789                         };
790
791                         this.POIs.AddReplace(playerNodePoi);
792                 }
793
794
795
796                 private void CommandListener(string eventName, ref EnumHandling handling, IAttribute data)
797                 {
798                         Logger.VerboseDebug("MsgBus RX: AutomapCommandMsg: {0}", data.ToJsonToken());
799
800                         CommandData cmdData = data as CommandData;
801
802
803                         if (CurrentState != CommandType.Snapshot)
804                         {
805                                 switch (cmdData.State)
806                                 {
807                                         case CommandType.Run:                                                                                           
808                                         case CommandType.Stop:
809                                                 if (CurrentState != cmdData.State) {
810                                                 CurrentState = cmdData.State;
811                                                 AwakenCartographer(0.0f);
812                                                 }
813                                                 break;
814
815                                         case CommandType.Snapshot:
816                                                 CurrentState = CommandType.Stop;
817                                                 //Snapshot starts a second thread/process...
818
819                                                 break;
820
821                                         case CommandType.Notation:
822                                                 //Add to POI list where player location
823                                                 AddNote(cmdData.Notation);
824                                                 break;
825                                 }
826                         }
827
828
829
830 #if DEBUG
831                         ClientAPI.TriggerChatMessage($"Automap commanded to: {cmdData.State} ");
832 #endif
833
834                 }
835
836
837                 #endregion
838
839         }
840
841 }