OSDN Git Service

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