2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Collections.ObjectModel;
9 using System.Text.RegularExpressions;
10 using System.Threading;
14 using Hjg.Pngcs.Chunks;
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;
26 public class AutomapSystem
28 private Thread cartographer_thread;
29 private ICoreClientAPI ClientAPI { get; set; }
30 private ILogger Logger { get; set; }
31 private IChunkRenderer ChunkRenderer { get; set; }
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);
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();
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; }
48 internal RunState CurrentState { get; set; }
49 //Run status, Chunks processed, stats, center of map....
50 private uint nullChunkCount, updatedChunksTotal;
51 private Vec2i startChunkColumn;
53 private readonly int chunkSize;
55 private IAsset stylesFile;
57 public static string AutomapStatusEventKey = @"AutomapStatus";
58 public static string AutomapCommandEventKey = @"AutomapCommand";
61 public AutomapSystem(ICoreClientAPI clientAPI, ILogger logger)
63 this.ClientAPI = clientAPI;
65 chunkSize = ClientAPI.World.BlockAccessor.ChunkSize;
66 ClientAPI.Event.LevelFinalize += EngageAutomap;
68 //TODO:Choose which one from GUI
69 this.ChunkRenderer = new StandardRenderer(clientAPI, logger);
71 //Listen on bus for commands
72 ClientAPI.Event.RegisterEventBusListener(CommandListener, 1.0, AutomapSystem.AutomapCommandEventKey);
78 private void EngageAutomap()
80 path = ClientAPI.GetOrCreateDataPath(_mapPath);
81 path = ClientAPI.GetOrCreateDataPath(Path.Combine(path, "World_" + ClientAPI.World.Seed));//Add name of World too...'ServerApi.WorldManager.CurrentWorldName'
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);
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);
90 Logger.Notification("AUTOMAP Start {0}", startChunkColumn);
93 ClientAPI.Event.ChunkDirty += ChunkAChanging;
95 cartographer_thread = new Thread(Cartographer);
96 cartographer_thread.Name = "Cartographer";
97 cartographer_thread.Priority = ThreadPriority.Lowest;
98 cartographer_thread.IsBackground = true;
100 ClientAPI.Event.RegisterGameTickListener(AwakenCartographer, 6000);
103 private void ChunkAChanging(Vec3i chunkCoord, IWorldChunk chunk, EnumChunkDirtyReason reason)
105 Vec2i topPosition = new Vec2i(chunkCoord.X, chunkCoord.Z);
107 columnCounter.AddOrUpdate(topPosition, 1, (key, colAct) => colAct + 1);
110 private void AwakenCartographer(float delayed)
113 if (CurrentState == RunState.Run && (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true))
116 Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState);
119 if (cartographer_thread.ThreadState.HasFlag(ThreadState.Unstarted))
121 cartographer_thread.Start();
123 else if (cartographer_thread.ThreadState.HasFlag(ThreadState.WaitSleepJoin))
125 //Time to (re)write chunk shards
126 cartographer_thread.Interrupt();
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})");
132 else if (CurrentState == RunState.Snapshot)
134 //TODO: Snapshot generator second thread...
140 private void Cartographer()
143 Logger.VerboseDebug("Cartographer thread awoken");
147 uint ejectedItem = 0;
148 uint updatedChunks = 0;
150 //-- Should dodge enumerator changing underfoot....at a cost.
151 if (!columnCounter.IsEmpty)
153 var tempSet = columnCounter.ToArray().OrderByDescending(kvp => kvp.Value);
154 foreach (var mostActiveCol in tempSet)
157 var mapChunk = ClientAPI.World.BlockAccessor.GetMapChunk(mostActiveCol.Key);
159 if (mapChunk == null)
161 Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!", mostActiveCol.Key);
163 columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
167 ColumnMeta chunkMeta = CreateColumnMetadata(mostActiveCol, mapChunk);
168 PngWriter pngWriter = SetupPngImage(mostActiveCol.Key, chunkMeta);
169 UpdateEntityMetadata();
170 ProcessChunkBlocks(mostActiveCol.Key, mapChunk, chunkMeta);
172 uint updatedPixels = 0;
174 ChunkRenderer.GenerateChunkPngShard(mostActiveCol.Key, mapChunk, chunkMeta, pngWriter, out updatedPixels);
176 if (updatedPixels > 0)
180 Logger.VerboseDebug("Wrote chunk shard: ({0}) - Edits#:{1}, Pixels#:{2}", mostActiveCol.Key, mostActiveCol.Value, updatedPixels);
183 chunkTopMetadata.Update(chunkMeta);
184 columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
188 columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
189 Logger.VerboseDebug("Un-painted chunk: ({0}) ", mostActiveCol.Key);
195 UpdateStatus(this.updatedChunksTotal, this.nullChunkCount, updatedChunks);
197 if (updatedChunks > 0)
199 //What about chunk updates themselves; a update bitmap isn't kept...
200 updatedChunksTotal += updatedChunks;
202 GenerateJSONMetadata();
206 //Then sleep until interupted again, and repeat
208 Logger.VerboseDebug("Thread '{0}' about to sleep indefinitely.", Thread.CurrentThread.Name);
210 Thread.Sleep(Timeout.Infinite);
213 catch (ThreadInterruptedException)
216 Logger.VerboseDebug("Thread '{0}' interupted [awoken]", Thread.CurrentThread.Name);
220 catch (ThreadAbortException)
222 Logger.VerboseDebug("Thread '{0}' aborted.", Thread.CurrentThread.Name);
227 Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name);
231 private void UpdateStatus(uint totalUpdates, uint voidChunks, uint delta)
233 StatusData updateData = new StatusData(totalUpdates, voidChunks, delta, RunState.Run);
235 this.ClientAPI.Event.PushEvent(AutomapStatusEventKey, updateData);
238 private void Prefill_POI_Designators()
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);
245 //Add special marker types for BlockID's of "Interest", overwrite colour, and method
247 Install_POI_Designators(DefaultDesignators.DefaultBlockDesignators(), DefaultDesignators.DefaultEntityDesignators());
250 private void Install_POI_Designators(ICollection<BlockDesignator> blockDesig, List<EntityDesignator> entDesig)
252 Logger.VerboseDebug("Connecting {0} standard Block-Designators", blockDesig.Count);
253 foreach (var designator in blockDesig)
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)
259 BlockID_Designators.Add(entry.Key, designator);
262 this.ChunkRenderer.BlockID_Designators = BlockID_Designators;
265 Logger.VerboseDebug("Connecting {0} standard Entity-Designators", entDesig.Count);
266 foreach (var designator in entDesig)
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));
271 foreach (var match in matched)
273 Logger.VerboseDebug("Linked Entity: {0} Designator: {1}", match.Code, designator);
274 this.Entity_Designators.Add(match.Code, designator);
279 //EntityProperties props = ClientAPI.World.GetEntityType(designator.Pattern);
286 private void GenerateMapHTML()
288 string mapFilename = Path.Combine(path, "Automap.html");
290 int TopNorth = chunkTopMetadata.North_mostChunk;
291 int TopSouth = chunkTopMetadata.South_mostChunk;
292 int TopEast = chunkTopMetadata.East_mostChunk;
293 int TopWest = chunkTopMetadata.West_mostChunk;
295 using (StreamWriter outputText = new StreamWriter(File.Open(mapFilename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)))
297 using (HtmlTextWriter tableWriter = new HtmlTextWriter(outputText))
299 tableWriter.BeginRender();
300 tableWriter.RenderBeginTag(HtmlTextWriterTag.Html);
302 tableWriter.RenderBeginTag(HtmlTextWriterTag.Head);
303 tableWriter.RenderBeginTag(HtmlTextWriterTag.Title);
304 tableWriter.WriteEncodedText("Generated Automap");
305 tableWriter.RenderEndTag();
307 tableWriter.RenderBeginTag(HtmlTextWriterTag.Style);
308 tableWriter.Write(stylesFile.ToText());
309 tableWriter.RenderEndTag();//</style>
311 //## JSON map-state data ######################
312 tableWriter.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
313 tableWriter.RenderBeginTag(HtmlTextWriterTag.Script);
315 tableWriter.Write("var available_images = [");
317 foreach (var shard in this.chunkTopMetadata)
319 tableWriter.Write("{{X:{0},Y:{1} }}, ", shard.Location.X, shard.Location.Y);
322 tableWriter.Write(" ];\n");
324 tableWriter.RenderEndTag();
326 tableWriter.RenderEndTag();
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();
341 //################ X-Axis <thead> #######################
342 tableWriter.RenderBeginTag(HtmlTextWriterTag.Thead);
343 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
345 tableWriter.RenderBeginTag(HtmlTextWriterTag.Th);
346 tableWriter.Write("N, W");
347 tableWriter.RenderEndTag();
349 for (int xAxisT = TopWest; xAxisT <= TopEast; xAxisT++)
351 tableWriter.RenderBeginTag(HtmlTextWriterTag.Th);
352 tableWriter.Write(xAxisT);
353 tableWriter.RenderEndTag();
356 tableWriter.RenderBeginTag(HtmlTextWriterTag.Th);
357 tableWriter.Write("N, E");
358 tableWriter.RenderEndTag();
360 tableWriter.RenderEndTag();
361 tableWriter.RenderEndTag();
362 //###### </thead> ################################
364 //###### <tbody> - Chunk rows & Y-axis cols
365 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tbody);
367 //######## <tr> for every vertical row
368 for (int yAxis = TopNorth; yAxis <= TopSouth; yAxis++)
370 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
371 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
372 tableWriter.Write(yAxis);//legend: Y-axis
373 tableWriter.RenderEndTag();
375 for (int xAxis = TopWest; xAxis <= TopEast; xAxis++)
377 //###### <td> #### for chunk shard
378 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
379 var colLoc = new Vec2i(xAxis, yAxis);
380 if (chunkTopMetadata.Contains(colLoc))
382 ColumnMeta meta = chunkTopMetadata[colLoc];
384 tableWriter.AddAttribute(HtmlTextWriterAttribute.Class, "tooltip");
385 tableWriter.RenderBeginTag(HtmlTextWriterTag.Div);
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);
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")}, ");
402 if (meta.RockRatio != null)
404 foreach (KeyValuePair<int, uint> blockID in meta.RockRatio)
406 var block = ClientAPI.World.GetBlock(blockID.Key);
407 tooltipText.AppendFormat(" {0} × {1},\t", block.Code.GetName(), meta.RockRatio[blockID.Key]);
411 tableWriter.WriteEncodedText(tooltipText.ToString());
413 tableWriter.RenderEndTag();//</span>
416 tableWriter.RenderEndTag();//</div> --tooltip enclosure
420 tableWriter.Write("?");
423 tableWriter.RenderEndTag();
424 }//############ </td> ###########
426 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
427 tableWriter.Write(yAxis);//legend: Y-axis
428 tableWriter.RenderEndTag();
430 tableWriter.RenderEndTag();
433 tableWriter.RenderEndTag();
435 //################ X-Axis <tfoot> #######################
436 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tfoot);
437 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
439 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
440 tableWriter.Write("S, W");
441 tableWriter.RenderEndTag();
443 for (int xAxisB = TopWest; xAxisB <= TopEast; xAxisB++)
445 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
446 tableWriter.Write(xAxisB);
447 tableWriter.RenderEndTag();
450 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
451 tableWriter.Write("S, E");
452 tableWriter.RenderEndTag();
454 tableWriter.RenderEndTag();
455 tableWriter.RenderEndTag();
456 //###### </tfoot> ################################
459 tableWriter.RenderEndTag();//</table>
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)
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();
475 foreach (var eoi in this.EOIs.PointsList)
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();
484 tableWriter.RenderEndTag();
489 tableWriter.RenderEndTag();//### </BODY> ###
491 tableWriter.EndRender();
497 Logger.VerboseDebug("Generated HTML map");
501 /// Generates the JSON Metadata. (in MAP object format )
503 private void GenerateJSONMetadata()
505 string jsonFilename = Path.Combine(path, "Metadata.js");
507 StreamWriter jsonWriter = new StreamWriter(jsonFilename, false, Encoding.UTF8);
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)
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);
535 jsonWriter.Write("}],");
537 jsonWriter.Write("]);\n\n");
540 jsonWriter.Write("let pointsOfInterest = new Map([");
541 foreach (var poi in POIs)
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("}],");
551 foreach (var poi in EOIs.PointsList)
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("}],");
560 jsonWriter.Write("]);\n\n");
568 private ColumnMeta CreateColumnMetadata(KeyValuePair<Vec2i, uint> mostActiveCol, IMapChunk mapChunk)
570 ColumnMeta data = new ColumnMeta(mostActiveCol.Key.Copy(), chunkSize);
571 BlockPos equivBP = new BlockPos(mostActiveCol.Key.X * chunkSize,
573 mostActiveCol.Key.Y * chunkSize);
575 var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP);
576 data.UpdateFieldsFrom(climate, mapChunk, TimeSpan.FromHours(ClientAPI.World.Calendar.TotalHours));
582 /// Reload chunk bounds from chunk shards
584 /// <returns>The metadata.</returns>
585 private void Reload_Metadata()
587 var worldmapDir = new DirectoryInfo(path);
589 if (worldmapDir.Exists)
592 var files = worldmapDir.GetFiles(chunkFile_filter);
594 if (files.Length > 0)
597 Logger.VerboseDebug("{0} Existing world chunk shards", files.Length);
602 foreach (var shardFile in files)
605 if (shardFile.Length < 512) continue;
606 var result = chunkShardRegex.Match(shardFile.Name);
609 int X_chunk_pos = int.Parse(result.Groups["X"].Value);
610 int Z_chunk_pos = int.Parse(result.Groups["Z"].Value);
612 //Parse PNG chunks for METADATA in shard
613 using (var fileStream = shardFile.OpenRead())
615 //TODO: Add corrupted PNG Exception handing HERE !
616 PngReader pngRead = new PngReader(fileStream);
617 pngRead.ReadSkippingAllRows();
620 PngMetadataChunk metadataFromPng = pngRead.GetChunksList().GetById1(PngMetadataChunk.ID) as PngMetadataChunk;
622 chunkTopMetadata.Add(metadataFromPng.ChunkMetadata);
633 Logger.VerboseDebug("Could not open world map directory");
641 private PngWriter SetupPngImage(Vec2i coord, ColumnMeta metadata)
643 ImageInfo imageInf = new ImageInfo(chunkSize, chunkSize, 8, false);
645 string filename = $"{coord.X}_{coord.Y}.png";
646 filename = Path.Combine(path, filename);
648 PngWriter pngWriter = FileHelper.CreatePngWriter(filename, imageInf, true);
649 PngMetadata meta = pngWriter.GetMetadata();
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);
662 /// Does the heavy lifting of Scanning columns of chunks - creates Heightmap and Processes POIs, Entities, and stats...
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)
670 int targetChunkY = mapChunk.YMax / chunkSize;//Surface ...
671 for (; targetChunkY > 0; targetChunkY--)
673 WorldChunk chunkData = ClientAPI.World.BlockAccessor.GetChunk(key.X, targetChunkY, key.Y) as WorldChunk;
675 if (chunkData == null || chunkData.BlockEntities == null)
678 Logger.VerboseDebug("Chunk null or empty X{0} Y{1} Z{2}", key.X, targetChunkY, key.Y);
683 /*************** Chunk Entities Scanning *********************/
684 if (chunkData.BlockEntities != null && chunkData.BlockEntities.Length > 0)
687 Logger.VerboseDebug("Surface@ {0} = BlockEntities: {1}", key, chunkData.BlockEntities.Length);
690 foreach (var blockEnt in chunkData.BlockEntities)
693 if (blockEnt != null && blockEnt.Block != null && BlockID_Designators.ContainsKey(blockEnt.Block.BlockId))
695 var designator = BlockID_Designators[blockEnt.Block.BlockId];
696 designator.SpecialAction(ClientAPI, POIs, blockEnt.Pos.Copy(), blockEnt.Block);
701 /********************* Chunk/Column BLOCKs scanning ****************/
702 //Heightmap, Stats, block tally
705 int X_index, Y_index, Z_index;
706 X_index = Y_index = Z_index = 0;
714 /* Encode packed indicie
715 (y * chunksize + z) * chunksize + x
717 var indicie = Helpers.ChunkBlockIndicie16(X_index, Y_index, Z_index);
718 int aBlockId = chunkData.Blocks[indicie];
722 chunkMeta.AirBlocks++;
726 if (RockIdCodes.ContainsKey(aBlockId))
728 if (chunkMeta.RockRatio.ContainsKey(aBlockId)) { chunkMeta.RockRatio[aBlockId]++; } else { chunkMeta.RockRatio.Add(aBlockId, 1); }
731 chunkMeta.NonAirBlocks++;
734 if (chunkMeta.HeightMap[X_index, Z_index] == 0)
735 { chunkMeta.HeightMap[X_index, Z_index] = (ushort) (Y_index + (targetChunkY * chunkSize)); }
738 while (X_index++ < (chunkSize - 1));
741 while (Z_index++ < (chunkSize - 1));
744 while (Y_index++ < (chunkSize - 1));
749 private void UpdateEntityMetadata()
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())
757 //Logger.VerboseDebug($"ENTITY: ({loadedEntity.Value.Code}) = #{loadedEntity.Value.EntityId} {loadedEntity.Value.State} {loadedEntity.Value.LocalPos} <<<<<<<<<<<<");
760 var dMatch = Entity_Designators.SingleOrDefault(se => se.Key.Equals(loadedEntity.Value.Code));
761 if (dMatch.Value != null)
763 dMatch.Value.SpecialAction(ClientAPI, this.EOIs, loadedEntity.Value.LocalPos.AsBlockPos.Copy(), loadedEntity.Value);
771 private void AddNote(string notation)
773 var playerNodePoi = new PointOfInterest()
775 Location = ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Copy(),
777 Timestamp = DateTimeOffset.UtcNow,
780 this.POIs.AddReplace(playerNodePoi);
785 private void CommandListener(string eventName, ref EnumHandling handling, IAttribute data)
787 Logger.VerboseDebug("MsgBus RX: AutomapCommandMsg: {0}", data.ToJsonToken());
789 CommandData cmdData = data as CommandData;
792 if (CurrentState != RunState.Snapshot)
794 switch (cmdData.State)
797 CurrentState = cmdData.State;
798 AwakenCartographer(0.0f);
802 CurrentState = cmdData.State;
805 case RunState.Snapshot:
806 CurrentState = RunState.Stop;
807 //Snapshot starts a second thread/process...
811 case RunState.Notation:
812 //Add to POI list where player location
813 AddNote(cmdData.Notation);
819 if (CurrentState != cmdData.State)
821 CurrentState = cmdData.State;
822 AwakenCartographer(0.0f);
826 ClientAPI.TriggerChatMessage($"Automap commanded to: {cmdData.State} ");