OSDN Git Service

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