OSDN Git Service

Events should attched after config data is loaded...
[automap/automap.git] / Automap / Subsystems / AutomapSystem.cs
1 using System;
2 using System.Collections;
3 using System.Collections.Concurrent;
4 using System.Collections.Generic;
5 using System.IO;
6 using System.Linq;
7 using System.Text;
8 using System.Text.RegularExpressions;
9 using System.Threading;
10
11 using Hjg.Pngcs;
12
13 using ProtoBuf;
14
15 using Vintagestory.API.Client;
16 using Vintagestory.API.Common;
17 using Vintagestory.API.Config;
18 using Vintagestory.API.Datastructures;
19 using Vintagestory.API.MathTools;
20 using Vintagestory.Common;
21
22 namespace Automap
23 {
24         public class AutomapSystem
25         {
26                 private Thread cartographer_thread;
27                 private Thread snapshotThread;
28                 private Snapshotter snapshot;
29                 private ICoreClientAPI ClientAPI { get; set; }
30                 private ILogger Logger { get; set; }
31                 private AChunkRenderer ChunkRenderer { get; set; }
32                 private JsonGenerator JsonGenerator { get; set; }
33
34                 internal const string _mapPath = @"Maps";
35                 internal const string _chunkPath = @"Chunks";
36                 internal const uint editThreshold = 1;
37                 private const string _domain = @"automap";
38                 private const string chunkFile_filter = @"*_*.png";
39                 private const string poiFileName = @"poi_binary";
40                 private const string eoiFileName = @"eoi_binary";
41                 private const string pointsTsvFileName = @"points_of_interest.tsv";
42                 private static Regex chunkShardRegex = new Regex(@"(?<X>[\d]+)_(?<Z>[\d]+)\.png", RegexOptions.Singleline);
43
44                 private ConcurrentDictionary<Vec2i, ColumnCounter> columnCounters = new ConcurrentDictionary<Vec2i, ColumnCounter>(3, 150);
45                 private ColumnsMetadata chunkTopMetadata;
46                 private PointsOfInterest POIs = new PointsOfInterest();
47                 private EntitiesOfInterest EOIs = new EntitiesOfInterest();
48
49                 internal Dictionary<int, BlockDesignator> BlockID_Designators { get; private set; }
50                 internal Dictionary<AssetLocation, EntityDesignator> Entity_Designators { get; private set; }
51                 internal Dictionary<int, string> RockIdCodes { get; private set; }
52                 internal Dictionary<int, string> AiryIdCodes { get; private set; }
53
54                 internal CommandType CurrentState { get; set; }
55                 //Run status, Chunks processed, stats, center of map....
56                 private uint nullChunkCount, nullMapCount, updatedChunksTotal;
57                 private Vec2i startChunkColumn;
58
59                 private readonly int chunkSize;
60                 private string path;
61                 private IAsset staticMap;
62                 private PersistedConfiguration configuration;
63
64
65                 public static string AutomapStatusEventKey = @"AutomapStatus";
66                 public static string AutomapCommandEventKey = @"AutomapCommand";
67
68                 public AutomapSystem(ICoreClientAPI clientAPI, ILogger logger, PersistedConfiguration config)
69                 {
70                         this.ClientAPI = clientAPI;
71                         this.Logger = logger;
72                         chunkSize = ClientAPI.World.BlockAccessor.ChunkSize;
73
74                         configuration = config;
75                         ClientAPI.Event.LevelFinalize += EngageAutomap;
76
77                         this.ChunkRenderer = InstantiateChosenRenderer(config.RendererName);
78
79                         //Listen on bus for commands
80                         ClientAPI.Event.RegisterEventBusListener(CommandListener, 1.0, AutomapSystem.AutomapCommandEventKey);
81
82
83                         if (configuration.Autostart)
84                         {
85                                 CurrentState = CommandType.Run;
86                                 Logger.Debug("Autostart is Enabled.");
87                         }
88
89                 }
90
91
92                 #region Internals
93                 private void EngageAutomap()
94                 {
95                         path = ClientAPI.GetOrCreateDataPath(_mapPath);
96                         path = ClientAPI.GetOrCreateDataPath(Path.Combine(path, "World_" + ClientAPI.World.Seed));//Add name of World too...'ServerApi.WorldManager.CurrentWorldName'
97                         ClientAPI.GetOrCreateDataPath(Path.Combine(path, _chunkPath));
98                                                   
99                         JsonGenerator = new JsonGenerator(ClientAPI, Logger, path);
100
101                         string mapFilename = Path.Combine(path, "automap.html");
102                         StreamWriter outputText = new StreamWriter(File.Open(mapFilename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite));
103
104                         staticMap = ClientAPI.World.AssetManager.Get(new AssetLocation(_domain, "config/automap.html"));
105                         outputText.Write(staticMap.ToText());
106                         outputText.Flush();
107
108                         Prefill_POI_Designators();
109                         startChunkColumn = new Vec2i((ClientAPI.World.Player.Entity.Pos.AsBlockPos.X / chunkSize), (ClientAPI.World.Player.Entity.Pos.AsBlockPos.Z / chunkSize));
110                         chunkTopMetadata = new ColumnsMetadata(startChunkColumn);
111                         Logger.Notification("AUTOMAP Start {0}", startChunkColumn);
112                         Reload_Metadata();
113
114                         ClientAPI.Event.ChunkDirty += ChunkAChanging;
115
116                         cartographer_thread = new Thread(Cartographer)
117                         {
118                                 Name = "Cartographer",
119                                 Priority = ThreadPriority.Lowest,
120                                 IsBackground = true
121                         };
122
123                         snapshot = new Snapshotter(path, chunkTopMetadata, chunkSize,ClientAPI.World.Seed );
124                         snapshotThread = new Thread(Snap)
125                         {
126                                 Name = "Snapshot",
127                                 Priority = ThreadPriority.Lowest,
128                                 IsBackground = true
129                         };
130
131                         ClientAPI.Event.RegisterGameTickListener(AwakenCartographer, 6000);
132                 }
133
134                 private void ChunkAChanging(Vec3i chunkCoord, IWorldChunk chunk, EnumChunkDirtyReason reason)
135                 {
136                 Vec2i topPosition = new Vec2i(chunkCoord.X, chunkCoord.Z);              
137                 bool newOrEdit = (reason == EnumChunkDirtyReason.NewlyCreated || reason == EnumChunkDirtyReason.NewlyLoaded);
138                 
139                 columnCounters.AddOrUpdate(topPosition, 
140                                               new ColumnCounter(chunkSize, newOrEdit, chunkCoord), 
141                                               (chkPos, chkChng) => chkChng.Update(chunkCoord, chunkSize, newOrEdit)
142                                              );
143                 
144                 }
145
146                 private void AwakenCartographer(float delayed)
147                 {
148
149                         if (CurrentState == CommandType.Run && (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true))
150                         {
151 #if DEBUG
152                                 Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState);
153 #endif
154
155                                 if (cartographer_thread.ThreadState.HasFlag(ThreadState.Unstarted))
156                                 {
157                                         cartographer_thread.Start();
158                                 }
159                                 else if (cartographer_thread.ThreadState.HasFlag(ThreadState.WaitSleepJoin))
160                                 {
161                                         //Time to (re)write chunk shards
162                                         cartographer_thread.Interrupt();
163                                 }
164                                 //#if DEBUG
165                                 //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})");
166                                 //#endif
167                         }
168                         else if (CurrentState == CommandType.Snapshot)
169                         {
170                                 if (snapshotThread.ThreadState.HasFlag(ThreadState.Unstarted))
171                                 {
172                                         snapshotThread.Start();
173                                 } else if (snapshotThread.ThreadState.HasFlag(ThreadState.WaitSleepJoin))
174                                 {
175                                         snapshotThread.Interrupt();
176                                 }
177                         }
178
179                 }
180
181
182                 private void Cartographer()
183                 {
184                         wake:
185                         Logger.VerboseDebug("Cartographer thread awoken");
186
187                         try
188                         {
189                                 ColumnCounter ejectedItem ;
190                                 uint updatedChunks = 0;
191                                 uint updatedPixels = 0;
192
193                                 //-- Should dodge enumerator changing underfoot....at a cost.
194                                 if (!columnCounters.IsEmpty)
195                                 {
196                                         var tempSet = columnCounters.ToArray().Where(cks => cks.Value.WeightedSum > editThreshold) .OrderByDescending(kvp => kvp.Value.WeightedSum);
197                                         UpdateEntityMetadata();
198
199                                         foreach (var mostActiveCol in tempSet)
200                                         {
201                                                 var mapChunk = ClientAPI.World.BlockAccessor.GetMapChunk(mostActiveCol.Key);
202
203                                                 if (mapChunk == null)
204                                                 {
205                                                         //TODO: REVISIT THIS CHUNK!
206                                                         Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!", mostActiveCol.Key);
207                                                         nullMapCount++;
208                                                         columnCounters.TryRemove(mostActiveCol.Key, out ejectedItem);
209                                                         continue;
210                                                 }
211
212                                                 ColumnMeta chunkMeta;
213                                                 if (chunkTopMetadata.Contains(mostActiveCol.Key))
214                                                 {
215                                                         chunkMeta = chunkTopMetadata[mostActiveCol.Key];
216                                                         #if DEBUG
217                                                         Logger.VerboseDebug("Loaded meta-chunk {0}", mostActiveCol.Key);
218                                                         #endif
219                                                 }
220                                                 else
221                                                 {
222                                                         chunkMeta = CreateColumnMetadata(mostActiveCol, mapChunk);
223                                                         #if DEBUG
224                                                         Logger.VerboseDebug("Created meta-chunk {0}", mostActiveCol.Key);
225                                                         #endif
226                                                 }
227                                                 ProcessChunkBlocks(mostActiveCol.Key, mapChunk, ref chunkMeta);
228
229                                                 ChunkRenderer.SetupPngImage(mostActiveCol.Key, path, _chunkPath, ref chunkMeta);
230                                                 ChunkRenderer.GenerateChunkPngShard(mostActiveCol.Key, mapChunk, chunkMeta, ref chunkTopMetadata, out updatedPixels);
231
232                                                 if (updatedPixels > 0)
233                                                 {
234                                                         #if DEBUG
235                                                         Logger.VerboseDebug("Wrote top-chunk shard: ({0}) - Weight:{1}, Pixels#:{2}", mostActiveCol.Key, mostActiveCol.Value, updatedPixels);
236                                                         #endif
237                                                         updatedChunks++;
238                                                         chunkTopMetadata.Update(chunkMeta);
239                                                         columnCounters.TryRemove(mostActiveCol.Key, out ejectedItem);
240                                                 }
241                                                 else
242                                                 {
243                                                         columnCounters.TryRemove(mostActiveCol.Key, out ejectedItem);
244                                                         #if DEBUG
245                                                         Logger.VerboseDebug("Un-painted chunk shard: ({0}) ", mostActiveCol.Key);
246                                                         #endif
247                                                 }
248                                         }
249                                 }
250
251                                 UpdateStatus(this.updatedChunksTotal, this.nullChunkCount, updatedChunks);
252
253                                 if (updatedChunks > 0)
254                                 {
255                                         //What about chunk updates themselves; a update bitmap isn't kept...
256                                         updatedChunksTotal += updatedChunks;
257                                         JsonGenerator.GenerateJSONMetadata(chunkTopMetadata, startChunkColumn, POIs, EOIs, RockIdCodes);
258                                         updatedChunks = 0;
259
260                                         //Cleanup in-memory Metadata...
261                                         chunkTopMetadata.ClearMetadata( );
262                                 }
263
264                                 #if DEBUG
265                                 Logger.VerboseDebug("Clearing Column Counters of: {0} non-written shards", columnCounters.Count);
266                                 #endif
267
268                                 columnCounters.Clear( );
269
270                                 //Then sleep until interupted again, and repeat
271 #if DEBUG
272                                 Logger.VerboseDebug("Thread '{0}' about to sleep indefinitely.", Thread.CurrentThread.Name);
273 #endif
274                                 Thread.Sleep(Timeout.Infinite);
275
276                         }
277                         catch (ThreadInterruptedException)
278                         {
279
280 #if DEBUG
281                                 Logger.VerboseDebug("Thread '{0}' interupted [awoken]", Thread.CurrentThread.Name);
282 #endif
283                                 goto wake;
284
285                         }
286                         catch (ThreadAbortException)
287                         {
288 #if DEBUG
289                                 Logger.VerboseDebug("Thread '{0}' aborted.", Thread.CurrentThread.Name);
290 #endif
291                         }
292                         finally
293                         {
294 #if DEBUG
295                                 Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name);
296 #endif
297                                 PersistPointsData();
298                         }
299                 }
300
301                 private void Snap()
302                 {
303                         snapshotTake:
304 #if DEBUG
305                         Logger.VerboseDebug("Snapshot started");
306 #endif
307                         try
308                         {
309                                 snapshot.Take();
310 #if DEBUG
311                                 Logger.VerboseDebug("Snapshot sleeping");
312 #endif
313                                 CurrentState = CommandType.Run;
314                                 Thread.Sleep(Timeout.Infinite);
315                         }
316                         catch (ThreadInterruptedException)
317                         {
318 #if DEBUG
319                                 Logger.VerboseDebug("Snapshot intertupted");
320 #endif
321                                 goto snapshotTake;
322                         }
323                 }
324
325                 private void UpdateStatus(uint totalUpdates, uint voidChunks, uint delta)
326                 {
327                         StatusData updateData = new StatusData(totalUpdates, voidChunks, delta, CommandType.Run);
328
329                         this.ClientAPI.Event.PushEvent(AutomapStatusEventKey, updateData);
330                 }
331
332                 private void Prefill_POI_Designators()
333                 {
334
335                         this.BlockID_Designators = new Dictionary<int, BlockDesignator>();
336                         this.Entity_Designators = new Dictionary<AssetLocation, EntityDesignator>();
337                         this.RockIdCodes = Helpers.ArbitrarytBlockIdHunter(ClientAPI, new AssetLocation(GlobalConstants.DefaultDomain, "rock-"), EnumBlockMaterial.Stone);
338
339                         var airBlocksQuery = from airyBlock in ClientAPI.World.Blocks
340                                                          where airyBlock.MatterState == EnumMatterState.Solid
341                                                          where airyBlock.BlockMaterial == EnumBlockMaterial.Plant || airyBlock.BlockMaterial == EnumBlockMaterial.Leaves
342                                                          where airyBlock.CollisionBoxes == null || airyBlock.CollisionBoxes.Length == 0
343                                                          select airyBlock;                      
344                         //^^ 'Solid' phase - 'Plant' Blocks without any boundg box ? Except water...
345                         this.AiryIdCodes = airBlocksQuery.ToDictionary(aBlk => aBlk.BlockId, aBlk => aBlk.Code.Path);
346
347                         //Add special marker types for BlockID's of "Interest", overwrite colour, and method
348                         Reload_POI_Designators();
349                 }
350
351                 private void Reload_POI_Designators()
352                 {
353                         Logger.VerboseDebug("Connecting {0} Configured Block-Designators", configuration.BlockDesignators.Count);
354                         foreach (var designator in configuration.BlockDesignators)
355                         {
356                                 var blockIDs = Helpers.ArbitrarytBlockIdHunter(ClientAPI, designator.Pattern, designator.Material);
357                                 if (blockIDs.Count > 0) { Logger.VerboseDebug("Designator {0} has {1} associated blockIDs", designator.ToString(), blockIDs.Count); }
358                                 foreach (var entry in blockIDs)
359                                 {
360                                         BlockID_Designators.Add(entry.Key, designator);
361                                 }
362                         }
363                         this.ChunkRenderer.BlockID_Designators = BlockID_Designators;
364
365
366                         Logger.VerboseDebug("Connecting {0} Configured Entity-Designators", configuration.EntityDesignators.Count);
367                         foreach (var designator in configuration.EntityDesignators)
368                         {
369                                 //Get Variants first, from EntityTypes...better be populated!
370                                 var matched = ClientAPI.World.EntityTypes.FindAll(entp => entp.Code.BeginsWith(designator.Pattern.Domain, designator.Pattern.Path));
371
372                                 foreach (var match in matched)
373                                 {
374                                         Logger.VerboseDebug("Linked Entity: {0} Designator: {1}", match.Code, designator);
375                                         this.Entity_Designators.Add(match.Code, designator);
376                                 }
377
378                                 //EntityProperties props = ClientAPI.World.GetEntityType(designator.Pattern);
379                         }
380
381
382                 }
383
384
385
386                 /// <summary>
387                 /// Store Points/Entity of Interest
388                 /// </summary>
389                 private void PersistPointsData()
390                 {
391                         //POI and EOI raw dump files ~ WRITE em!
392                         //var poiRawFile = File.
393                         string poiPath = Path.Combine(path, poiFileName);
394                         string eoiPath = Path.Combine(path, eoiFileName);
395
396                         if (this.POIs.Count > 0)
397                         {
398                                 using (var poiFile = File.OpenWrite(poiPath))
399                                 {
400                                         Serializer.Serialize<PointsOfInterest>(poiFile, this.POIs);
401                                 }
402                         }
403
404                         if (this.EOIs.Count > 0)
405                         {
406                                 using (var eoiFile = File.OpenWrite(eoiPath))
407                                 {
408                                         Serializer.Serialize<EntitiesOfInterest>(eoiFile, this.EOIs);
409                                 }
410                         }
411
412                         //Create Easy to Parse TSV file for tool/human use....
413                         string pointsTsvPath = Path.Combine(path, pointsTsvFileName);
414
415                         using (var tsvWriter = new StreamWriter(pointsTsvPath, false, Encoding.UTF8))
416                         {
417                                 tsvWriter.WriteLine("Name\tDescription\tLocation\tTime\tDestination");
418                                 foreach (var point in this.POIs)
419                                 {
420                                         tsvWriter.Write(point.Name + "\t");
421                                         var notes = point.Notes
422                                                 .Replace("\n", "\\n")
423                                                 .Replace("\t", "\\t")
424                                                 .Replace("\\", "\\\\");
425                                         tsvWriter.Write(notes + "\t");
426                                         tsvWriter.Write(point.Location.PrettyCoords(ClientAPI) + "\t");
427                                         tsvWriter.Write(point.Timestamp.ToString("u") + "\t");
428                                         tsvWriter.Write((point.Destination != null ? point.Destination.PrettyCoords(ClientAPI) : "---") +"\t");
429                                         tsvWriter.WriteLine();
430                                 }
431                                 foreach (var entity in this.EOIs)
432                                 {
433                                         tsvWriter.Write(entity.Name + "\t");
434                                         var notes = entity.Notes
435                                                 .Replace("\n", "\\n")
436                                                 .Replace("\t", "\\t")
437                                                 .Replace("\\", "\\\\");
438                                         tsvWriter.Write(notes + "\t");
439                                         tsvWriter.Write(entity.Location.PrettyCoords(ClientAPI) + "\t");
440                                         tsvWriter.Write(entity.Timestamp.ToString("u") + "\t");
441                                         tsvWriter.Write("n/a\t");
442                                         tsvWriter.WriteLine();
443                                 }
444                                 tsvWriter.WriteLine();
445                                 tsvWriter.Flush();
446                         }
447
448                 }
449
450                 private ColumnMeta CreateColumnMetadata(KeyValuePair<Vec2i, ColumnCounter> mostActiveCol, IMapChunk mapChunk)
451                 {
452                         ColumnMeta data = new ColumnMeta(mostActiveCol.Key.Copy(), ClientAPI, (byte) chunkSize, (ClientAPI.World.BlockAccessor.MapSizeY / chunkSize));
453                         BlockPos equivBP = new BlockPos(mostActiveCol.Key.X * chunkSize,
454                                                                                         mapChunk.YMax,
455                                                                                         mostActiveCol.Key.Y * chunkSize);
456
457                         var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP);
458                         data.UpdateFieldsFrom(climate, mapChunk, TimeSpan.FromHours(ClientAPI.World.Calendar.TotalHours));
459
460                         return data;
461                 }
462
463                 /// <summary>
464                 /// Reload chunk bounds from chunk shards
465                 /// </summary>
466                 /// <returns>The metadata.</returns>
467                 private void Reload_Metadata()
468                 {
469                         var shardsDir = new DirectoryInfo( Path.Combine(path, _chunkPath) );
470
471                         if (!shardsDir.Exists)
472                         {
473                                 #if DEBUG
474                                 Logger.VerboseDebug("Could not open world map (shards) directory");
475                                 #endif
476                                 return;
477                         }
478                         var shardFiles = shardsDir.GetFiles(chunkFile_filter);
479
480                         if (shardFiles.Length > 0)
481                         {
482                                 #if DEBUG
483                                 Logger.VerboseDebug("Metadata reloading from {0} shards", shardFiles.Length);
484                                 #endif
485
486                                 foreach (var shardFile in shardFiles)
487                                 {
488
489                                         if (shardFile.Length < 1024) continue;
490                                         var result = chunkShardRegex.Match(shardFile.Name);
491                                         if (!result.Success) continue;
492
493                                         int X_chunk_pos = int.Parse(result.Groups["X"].Value);
494                                         int Z_chunk_pos = int.Parse(result.Groups["Z"].Value);
495
496                                         try
497                                         {
498                                                 using (var fileStream = shardFile.OpenRead())
499                                                 {
500
501                                                         PngReader pngRead = new PngReader(fileStream);
502                                                         pngRead.ReadSkippingAllRows();
503                                                         pngRead.End();
504                                                         //Parse PNG chunks for METADATA in shard
505                                                         PngMetadataChunk metadataFromPng = pngRead.GetChunksList().GetById1(PngMetadataChunk.ID) as PngMetadataChunk;
506                                                         var column = metadataFromPng.ChunkMetadata;
507                                                         if (column.PrettyLocation == null)
508                                                                 column = column.Reload(ClientAPI);
509                                                         chunkTopMetadata.Add(column);
510                                                 }
511
512                                         }
513                                         catch (PngjException someEx)
514                                         {
515                                                 Logger.Error("PNG Corruption file '{0}' - Reason: {1}", shardFile.Name, someEx);
516                                                 continue;
517                                         }
518                                         catch (ProtoException protoEx) 
519                                         {
520                                                 Logger.Error("ProtoBuf invalid! file:'{0}' - Reason: {1}", shardFile.Name, protoEx);
521                                                 continue;
522                                         }
523                                 }
524                         }
525
526                         //POI and EOI raw dump files ~ reload em!
527                         //var poiRawFile = File.
528                         string poiPath = Path.Combine(path, poiFileName);
529                         string eoiPath = Path.Combine(path, eoiFileName);
530
531                         if (File.Exists(poiPath))
532                         {
533                                 using (var poiFile = File.OpenRead(poiPath))
534                                 {
535                                         this.POIs = Serializer.Deserialize<PointsOfInterest>(poiFile);
536                                         Logger.VerboseDebug("Reloaded {0} POIs from file.", this.POIs.Count);
537                                 }
538                         }
539
540                         if (File.Exists(eoiPath))
541                         {
542                                 using (var eoiFile = File.OpenRead(eoiPath))
543                                 {
544                                         this.EOIs = Serializer.Deserialize<EntitiesOfInterest>(eoiFile);
545                                         Logger.VerboseDebug("Reloaded {0} EOIs from file.", this.EOIs.Count);
546                                 }
547                         }
548
549                 }
550
551
552
553                 /// <summary>
554                 /// Does the heavy lifting of Scanning columns of chunks - scans for BlockEntity, creates Heightmap and stats...
555                 /// </summary>
556                 /// <param name="key">Chunk Coordinate</param>
557                 /// <param name="mapChunk">Map chunk.</param>
558                 /// <param name="chunkMeta">Chunk metadata</param>
559                 private void ProcessChunkBlocks(Vec2i key, IMapChunk mapChunk, ref ColumnMeta chunkMeta)
560                 {
561                         int targetChunkY = mapChunk.YMax / chunkSize;//Surface ish... 
562                         byte chunkTally = 0;
563
564                 #if DEBUG
565                 Logger.VerboseDebug("Start col @ X{0} Y{1} Z{2} !", key.X, targetChunkY, key.Y);
566                 #endif
567
568                 chunkMeta.ResetMetadata(ClientAPI.World.BlockAccessor.MapSizeY);
569
570                 for (; targetChunkY > 0; targetChunkY--)
571                         {
572                                 WorldChunk worldChunk = ClientAPI.World.BlockAccessor.GetChunk(key.X, targetChunkY, key.Y) as WorldChunk;
573
574                                 if (worldChunk == null || worldChunk.BlockEntities == null)
575                                 {
576                                         #if DEBUG
577                                         Logger.VerboseDebug("WORLD chunk: null or empty X{0} Y{1} Z{2} !", key.X, targetChunkY, key.Y);
578                                         #endif
579                                         nullChunkCount++;
580                                         continue;
581                                 }
582
583                                 if (worldChunk.IsPacked()) 
584                                 {
585                                 Logger.VerboseDebug("WORLD chunk: Compressed: X{0} Y{1} Z{2}", key.X, targetChunkY, key.Y);
586                                 worldChunk.Unpack( );//RESEARCH: Thread Unsafe? 
587                                 }
588
589                                 /*************** Chunk Entities Scanning *********************/
590                                 if (worldChunk.BlockEntities != null && worldChunk.BlockEntities.Count > 0)
591                                 {
592                                         #if DEBUG
593                                         Logger.VerboseDebug("Scan pos.({0}) for BlockEntities# {1}", key, worldChunk.BlockEntities.Count);
594                                         #endif
595
596                                         foreach (var blockEnt in worldChunk.BlockEntities)
597                                         {
598                                                 if (blockEnt.Value != null && blockEnt.Value.Block != null && BlockID_Designators.ContainsKey(blockEnt.Value.Block.BlockId))
599                                                 {
600                                                         var designator = BlockID_Designators[blockEnt.Value.Block.BlockId];
601                                                         designator.SpecialAction(ClientAPI, POIs, blockEnt.Value.Pos.Copy(), blockEnt.Value.Block);
602                                                 }
603                                         }
604                                 }
605
606                                 /********************* Chunk/Column BLOCKs scanning ****************/
607                                 //Heightmap, Stats, block tally
608
609                                 int X_index, Y_index, Z_index;
610
611                                 //First Chance fail-safe;
612                                 if (worldChunk.Blocks == null || worldChunk.Blocks.Length <= 0) {
613                                 Logger.VerboseDebug("WORLD chunk; Missing block DATA⁈ X{0} Y{1} Z{2} ⁈", key.X, targetChunkY, key.Y);
614                                 nullChunkCount++;
615                                 continue;
616                                 }               
617
618                                 chunkMeta.ColumnPresense[targetChunkY] = true;
619                                 chunkTally++;
620                                 for (Y_index = 0; Y_index < chunkSize; Y_index++)
621                                 {
622                                         for (Z_index = 0; Z_index < chunkSize; Z_index++)
623                                         {
624                                                 for (X_index = 0; X_index < chunkSize; X_index++) 
625                                                 {
626                                                 var indicie = MapUtil.Index3d(X_index, Y_index, Z_index, chunkSize, chunkSize);
627
628                                                 //'Last' Chance fail-safe;
629                                                 if (worldChunk.Blocks == null || worldChunk.Blocks.Length <= 0) {
630                                                 Logger.VerboseDebug("Processing Block: Missing block DATA⁈ X{0} Y{1} Z{2} ⁈", X_index, Y_index, Z_index);
631                                                 nullChunkCount++;
632                                                 goto loop_bustout; ;
633                                                 }
634
635                                                 int aBlockId = worldChunk.Blocks[indicie];
636
637                                                 if (aBlockId == 0 || AiryIdCodes.ContainsKey(aBlockId)) {//Airy blocks,,,
638                                                 chunkMeta.AirBlocks++;
639                                                 continue;
640                                                 }
641
642                                                 if (RockIdCodes.ContainsKey(aBlockId)) {
643                                                 if (chunkMeta.RockRatio.ContainsKey(aBlockId))
644                                                         chunkMeta.RockRatio[aBlockId]++;
645                                                 else
646                                                         chunkMeta.RockRatio.Add(aBlockId, 1);
647                                                 }
648
649                                                 chunkMeta.NonAirBlocks++;
650
651                                                 ushort localHeight = ( ushort )(Y_index + (targetChunkY * chunkSize));
652                                                 //Heightmap - Need to ignore Grass & Snow
653                                                 if (localHeight > chunkMeta.HeightMap[X_index, Z_index]) 
654                                                         {
655                                                         chunkMeta.HeightMap[X_index, Z_index] = localHeight;
656                                                         if (localHeight > chunkMeta.YMax) chunkMeta.YMax = localHeight;
657                                                         }
658                                                 }
659                                         }
660                                 }
661                                 loop_bustout:;
662                         }
663                         Logger.VerboseDebug("COLUMN X{0} Z{1}: {2}, processed.", key.X , key.Y, chunkTally + 1);
664                 }
665
666                 private void UpdateEntityMetadata()
667                 {
668                         Logger.Debug("Presently {0} Entities", ClientAPI.World.LoadedEntities.Count);
669                         //Mabey scan only for 'new' entities by tracking ID in set?
670                         foreach (var loadedEntity in ClientAPI.World.LoadedEntities.ToArray())
671                         {
672
673                                 #if DEBUG
674                                 //Logger.VerboseDebug($"ENTITY: ({loadedEntity.Value.Code}) = #{loadedEntity.Value.EntityId} {loadedEntity.Value.State} {loadedEntity.Value.LocalPos}    <<<<<<<<<<<<");
675                                 #endif
676
677                                 var dMatch = Entity_Designators.SingleOrDefault(se => se.Key.Equals(loadedEntity.Value.Code));
678                                 if (dMatch.Value != null)
679                                 {
680                                         dMatch.Value.SpecialAction(ClientAPI, this.EOIs, loadedEntity.Value.Pos.AsBlockPos.Copy(), loadedEntity.Value);
681                                 }
682
683                         }
684
685
686                 }
687
688                 private void AddNote(string notation)
689                 {
690                         var playerNodePoi = new PointOfInterest()
691                         {
692                                 Name = "Note",
693                                 Location = ClientAPI.World.Player.Entity.Pos.AsBlockPos.Copy(),
694                                 Notes = notation,
695                                 Timestamp = DateTime.UtcNow,
696                         };
697
698                         this.POIs.AddReplace(playerNodePoi);
699                 }
700
701
702
703                 private void CommandListener(string eventName, ref EnumHandling handling, IAttribute data)
704                 {
705                         //Logger.VerboseDebug("MsgBus RX: AutomapCommandMsg: {0}", data.ToJsonToken());
706
707                         CommandData cmdData = data as CommandData;
708
709                         switch (cmdData.State)
710                         {
711                                 case CommandType.Run:
712                                 case CommandType.Stop:
713                                 case CommandType.Snapshot:
714                                         if (CurrentState != cmdData.State)
715                                         {
716                                                 CurrentState = cmdData.State;
717                                                 AwakenCartographer(0.0f);
718                                         }
719                                         break;
720
721                                 case CommandType.Notation:
722                                         //Add to POI list where player location
723                                         AddNote(cmdData.Notation);
724                                         break;
725                         }
726                         #if DEBUG
727                         ClientAPI.TriggerChatMessage($"Automap commanded to: {cmdData.State} ");
728                         #endif
729                 }
730                 #endregion
731
732                 private AChunkRenderer InstantiateChosenRenderer(string rendererName )
733                 {
734                 Logger.VerboseDebug("Using '{0}' style Shard Renderer", rendererName);
735                 switch (rendererName) 
736                 {                               
737                 case StandardRenderer.Name:
738                         return new StandardRenderer(ClientAPI, Logger, this.configuration.SeasonalColors);
739                 
740                 case AlternateRenderer.Name:
741                         return new AlternateRenderer(ClientAPI, Logger, this.configuration.SeasonalColors);
742         
743                 case FlatRenderer.Name:
744                         return new FlatRenderer(ClientAPI, Logger, this.configuration.SeasonalColors);  
745
746                 default:
747                         throw new ArgumentOutOfRangeException("rendererName",rendererName,"That value isn't supported or known...");
748                 }
749
750                 return null;
751                 }
752         }
753
754 }