X-Git-Url: http://git.osdn.net/view?p=automap%2Fautomap.git;a=blobdiff_plain;f=Automap%2FSubsystems%2FAutomapSystem.cs;h=abbfc44ecb1fe155f5a93f3893e8f1011e9a6115;hp=6dfa240a45048bff43e691499c52bd8a2bd2f8e3;hb=a5cf48106e5ad88de4153de6393b122c5096c3fb;hpb=c76975a1cd65eff40a64e85eb95139b774fe8c13 diff --git a/Automap/Subsystems/AutomapSystem.cs b/Automap/Subsystems/AutomapSystem.cs index 6dfa240..abbfc44 100644 --- a/Automap/Subsystems/AutomapSystem.cs +++ b/Automap/Subsystems/AutomapSystem.cs @@ -1,741 +1,801 @@ using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.ObjectModel; - using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; -using System.Web.UI; using Hjg.Pngcs; -using Hjg.Pngcs.Chunks; + +using ProtoBuf; using Vintagestory.API.Client; using Vintagestory.API.Common; -using Vintagestory.API.Common.Entities; using Vintagestory.API.Config; using Vintagestory.API.Datastructures; using Vintagestory.API.MathTools; using Vintagestory.Common; namespace Automap -{ +{ public class AutomapSystem { private Thread cartographer_thread; + private Thread snapshotThread; + private Snapshotter snapshot; private ICoreClientAPI ClientAPI { get; set; } private ILogger Logger { get; set; } - private IChunkRenderer ChunkRenderer { get; set; } + private AChunkRenderer ChunkRenderer { get; set; } + private JsonGenerator JsonGenerator { get; set; } - private const string _mapPath = @"Maps"; - private const string _chunkPath = @"Chunks"; + internal const string _mapPath = @"Maps"; + internal const string _chunkPath = @"Chunks"; + internal const uint editThreshold = 9; private const string _domain = @"automap"; private const string chunkFile_filter = @"*_*.png"; - private static Regex chunkShardRegex = new Regex(@"(?[\d]+)_(?[\d]+).png", RegexOptions.Singleline); + private const string poiFileName = @"poi_binary"; + private const string eoiFileName = @"eoi_binary"; + private const string pointsTsvFileName = @"points_of_interest.tsv"; + private const string plainMetadataFileName = @"map_metadata.txt"; + private static Regex chunkShardRegex = new Regex(@"(?[\d]+)_(?[\d]+)\.png", RegexOptions.Singleline); - private ConcurrentDictionary columnCounter = new ConcurrentDictionary(3, 150 ); + private ConcurrentDictionary columnCounters = new ConcurrentDictionary(3, 150); private ColumnsMetadata chunkTopMetadata; - private PointsOfInterest POIs; - private EntitiesOfInterest EOIs; + internal PointsOfInterest POIs = new PointsOfInterest(); + internal EntitiesOfInterest EOIs = new EntitiesOfInterest(); - internal Dictionary BlockID_Designators { get; private set;} + internal Dictionary BlockID_Designators { get; private set; } internal Dictionary Entity_Designators { get; private set; } internal Dictionary RockIdCodes { get; private set; } + internal Dictionary AiryIdCodes { get; private set; } - internal RunState CurrentState { get; set; } + internal CommandType CurrentState { get; set; } //Run status, Chunks processed, stats, center of map.... - private uint nullChunkCount, updatedChunksTotal; + private uint nullChunkCount, nullMapCount, updatedChunksTotal; private Vec2i startChunkColumn; private readonly int chunkSize; private string path; - private IAsset stylesFile; + private IAsset staticMap; + private PersistedConfiguration configuration; + public static string AutomapStatusEventKey = @"AutomapStatus"; public static string AutomapCommandEventKey = @"AutomapCommand"; - - public AutomapSystem(ICoreClientAPI clientAPI, ILogger logger) + public AutomapSystem(ICoreClientAPI clientAPI, ILogger logger, PersistedConfiguration config) { - this.ClientAPI = clientAPI; - this.Logger = logger; - chunkSize = ClientAPI.World.BlockAccessor.ChunkSize; - ClientAPI.Event.LevelFinalize += EngageAutomap; + this.ClientAPI = clientAPI; + this.Logger = logger; + chunkSize = ClientAPI.World.BlockAccessor.ChunkSize; - //TODO:Choose which one from GUI - this.ChunkRenderer = new StandardRenderer(clientAPI, logger); + configuration = config; + ClientAPI.Event.LevelFinalize += EngageAutomap; - //Listen on bus for commands - ClientAPI.Event.RegisterEventBusListener(CommandListener, 1.0, AutomapSystem.AutomapCommandEventKey); + this.ChunkRenderer = InstantiateChosenRenderer(config.RendererName); + + //Listen on bus for commands + ClientAPI.Event.RegisterEventBusListener(CommandListener, 1.0, AutomapSystem.AutomapCommandEventKey); + + + if (configuration.Autostart) + { + CurrentState = CommandType.Run; + Logger.Debug("Autostart is Enabled."); + } } #region Internals - private void EngageAutomap( ) + private void EngageAutomap() { - path = ClientAPI.GetOrCreateDataPath(_mapPath); - path = ClientAPI.GetOrCreateDataPath(Path.Combine(path, "World_" + ClientAPI.World.Seed));//Add name of World too...'ServerApi.WorldManager.CurrentWorldName' + path = ClientAPI.GetOrCreateDataPath(_mapPath); + path = ClientAPI.GetOrCreateDataPath(Path.Combine(path, "World_" + ClientAPI.World.Seed));//Add name of World too...'ServerApi.WorldManager.CurrentWorldName' + ClientAPI.GetOrCreateDataPath(Path.Combine(path, _chunkPath)); + + JsonGenerator = new JsonGenerator(ClientAPI, Logger, path); + + string mapFilename = Path.Combine(path, "automap.html"); + StreamWriter outputText = new StreamWriter(File.Open(mapFilename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)); - stylesFile = ClientAPI.World.AssetManager.Get(new AssetLocation(_domain, "config/automap_format.css")); - Logger.VerboseDebug("CSS loaded: {0} size: {1}",stylesFile.IsLoaded() ,stylesFile.ToText( ).Length); + staticMap = ClientAPI.World.AssetManager.Get(new AssetLocation(_domain, "config/automap.html")); + outputText.Write(staticMap.ToText()); + outputText.Flush(); - Prefill_POI_Designators( ); - startChunkColumn = new Vec2i((ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.X / chunkSize), (ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Z / chunkSize)); - chunkTopMetadata = new ColumnsMetadata(startChunkColumn); + Prefill_POI_Designators(); + startChunkColumn = new Vec2i((ClientAPI.World.Player.Entity.Pos.AsBlockPos.X / chunkSize), (ClientAPI.World.Player.Entity.Pos.AsBlockPos.Z / chunkSize)); + chunkTopMetadata = new ColumnsMetadata(startChunkColumn); + Logger.Notification("AUTOMAP Start {0}", startChunkColumn); + Reload_Metadata(); - Logger.Notification("AUTOMAP Start {0}", startChunkColumn); - Reload_Metadata( ); + ClientAPI.Event.ChunkDirty += ChunkAChanging; - ClientAPI.Event.ChunkDirty += ChunkAChanging; + cartographer_thread = new Thread(Cartographer) + { + Name = "Cartographer", + Priority = ThreadPriority.Lowest, + IsBackground = true + }; - cartographer_thread = new Thread(Cartographer); - cartographer_thread.Name = "Cartographer"; - cartographer_thread.Priority = ThreadPriority.Lowest; - cartographer_thread.IsBackground = true; + snapshot = new Snapshotter(path, chunkTopMetadata, chunkSize,ClientAPI.World.Seed ); + snapshotThread = new Thread(Snap) + { + Name = "Snapshot", + Priority = ThreadPriority.Lowest, + IsBackground = true + }; - ClientAPI.Event.RegisterGameTickListener(AwakenCartographer, 6000); + ClientAPI.Event.RegisterGameTickListener(AwakenCartographer, 6000); } private void ChunkAChanging(Vec3i chunkCoord, IWorldChunk chunk, EnumChunkDirtyReason reason) - { - Vec2i topPosition = new Vec2i(chunkCoord.X, chunkCoord.Z); - - columnCounter.AddOrUpdate(topPosition, 1, (key, colAct) => colAct + 1); + { + Vec2i topPosition = new Vec2i(chunkCoord.X, chunkCoord.Z); + bool newOrEdit = (reason == EnumChunkDirtyReason.NewlyCreated || reason == EnumChunkDirtyReason.NewlyLoaded); + + columnCounters.AddOrUpdate(topPosition, + new ColumnCounter(chunkSize, newOrEdit, chunkCoord), + (chkPos, chkChng) => chkChng.Update(chunkCoord, chunkSize, newOrEdit) + ); + } private void AwakenCartographer(float delayed) { - if (CurrentState == RunState.Run && (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true)) { - #if DEBUG - Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState); - #endif + if (CurrentState == CommandType.Run && (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true)) + { +#if DEBUG + Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState); +#endif - if (cartographer_thread.ThreadState.HasFlag(ThreadState.Unstarted)) { - cartographer_thread.Start( ); - } - else if (cartographer_thread.ThreadState.HasFlag(ThreadState.WaitSleepJoin)) { - //Time to (re)write chunk shards - cartographer_thread.Interrupt( ); - } - //#if DEBUG - //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})"); - //#endif - } + if (cartographer_thread.ThreadState.HasFlag(ThreadState.Unstarted)) + { + cartographer_thread.Start(); + } + else if (cartographer_thread.ThreadState.HasFlag(ThreadState.WaitSleepJoin)) + { + //Time to (re)write chunk shards + cartographer_thread.Interrupt(); + } + //#if DEBUG + //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})"); + //#endif + } + else if (CurrentState == CommandType.Snapshot) + { + if (snapshotThread.ThreadState.HasFlag(ThreadState.Unstarted)) + { + snapshotThread.Start(); + } else if (snapshotThread.ThreadState.HasFlag(ThreadState.WaitSleepJoin)) + { + snapshotThread.Interrupt(); + } + } } - private void Cartographer( ) + private void Cartographer() { - wake: - Logger.VerboseDebug("Cartographer thread awoken"); - - try { - uint ejectedItem = 0; - uint updatedChunks = 0; - - //-- Should dodge enumerator changing underfoot....at a cost. - if (!columnCounter.IsEmpty) { - var tempSet = columnCounter.ToArray( ).OrderByDescending(kvp => kvp.Value); - foreach (var mostActiveCol in tempSet) { - - var mapChunk = ClientAPI.World.BlockAccessor.GetMapChunk(mostActiveCol.Key); - - if (mapChunk == null) { - Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!", mostActiveCol.Key); - nullChunkCount++; - columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem ); - continue; - } - - ColumnMeta chunkMeta = CreateColumnMetadata(mostActiveCol,mapChunk); - PngWriter pngWriter = SetupPngImage(mostActiveCol.Key, chunkMeta); - UpdateEntityMetadata( ); - ProcessChunkBlocks(mostActiveCol.Key, mapChunk, chunkMeta); + wake: + Logger.VerboseDebug("Cartographer thread awoken"); - uint updatedPixels = 0; - - ChunkRenderer.GenerateChunkPngShard(mostActiveCol.Key, mapChunk, chunkMeta, pngWriter , out updatedPixels); - - if (updatedPixels > 0) { - - #if DEBUG - Logger.VerboseDebug("Wrote chunk shard: ({0}) - Edits#:{1}, Pixels#:{2}", mostActiveCol.Key, mostActiveCol.Value, updatedPixels); - #endif - updatedChunks++; - chunkTopMetadata.Update(chunkMeta); - columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem); - } - else { - columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem); - Logger.VerboseDebug("Un-painted chunk: ({0}) ", mostActiveCol.Key); - } + try + { + ColumnCounter ejectedItem ; + uint updatedChunks = 0; + uint updatedPixels = 0; - } - } + //-- Should dodge enumerator changing underfoot....at a cost. + if (!columnCounters.IsEmpty) + { + var tempSet = columnCounters.ToArray().Where(cks => cks.Value.WeightedSum > editThreshold) .OrderByDescending(kvp => kvp.Value.WeightedSum); + UpdateEntityMetadata(); + + foreach (var mostActiveCol in tempSet) + { + var mapChunk = ClientAPI.World.BlockAccessor.GetMapChunk(mostActiveCol.Key); + + if (mapChunk == null) + { + //TODO: REVISIT THIS CHUNK! + #if DEBUG + Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!", mostActiveCol.Key); + #endif + nullMapCount++; + columnCounters.TryRemove(mostActiveCol.Key, out ejectedItem); + continue; + } + + ColumnMeta chunkMeta; + if (chunkTopMetadata.Contains(mostActiveCol.Key)) + { + chunkMeta = chunkTopMetadata[mostActiveCol.Key]; + #if DEBUG + Logger.VerboseDebug("Loaded meta-chunk {0}", mostActiveCol.Key); + #endif + } + else + { + chunkMeta = CreateColumnMetadata(mostActiveCol, mapChunk); + #if DEBUG + Logger.VerboseDebug("Created meta-chunk {0}", mostActiveCol.Key); + #endif + } + ProcessChunkBlocks(mostActiveCol.Key, mapChunk, ref chunkMeta); + mostActiveCol.Value.SetCutoff(chunkMeta.YMax / chunkSize); + + ChunkRenderer.SetupPngImage(mostActiveCol.Key, path, _chunkPath, ref chunkMeta); + ChunkRenderer.GenerateChunkPngShard(mostActiveCol.Key, mapChunk, chunkMeta, ref chunkTopMetadata, out updatedPixels); + + if (updatedPixels > 0) + { + #if DEBUG + Logger.VerboseDebug("Wrote top-chunk shard: ({0}) - Weight:{1}, Pixels#:{2}", mostActiveCol.Key, mostActiveCol.Value, updatedPixels); + #endif + updatedChunks++; + chunkTopMetadata.Update(chunkMeta); + columnCounters.TryRemove(mostActiveCol.Key, out ejectedItem); + } + else + { + columnCounters.TryRemove(mostActiveCol.Key, out ejectedItem); + #if DEBUG + Logger.VerboseDebug("Un-painted chunk shard: ({0}) ", mostActiveCol.Key); + #endif + } + } + } - UpdateStatus(this.updatedChunksTotal, this.nullChunkCount, updatedChunks); + UpdateStatus(this.updatedChunksTotal, this.nullChunkCount, updatedChunks); - if (updatedChunks > 0) { - //What about chunk updates themselves; a update bitmap isn't kept... - updatedChunksTotal += updatedChunks; - GenerateMapHTML( ); - GenerateJSONMetadata( ); - updatedChunks = 0; - } + if (updatedChunks > 0) + { + //What about chunk updates themselves; a update bitmap isn't kept... + updatedChunksTotal += updatedChunks; + JsonGenerator.GenerateJSONMetadata(chunkTopMetadata, startChunkColumn, POIs, EOIs, RockIdCodes); + updatedChunks = 0; - //Then sleep until interupted again, and repeat + //Cleanup in-memory Metadata... + chunkTopMetadata.ClearMetadata( ); + } - Logger.VerboseDebug("Thread '{0}' about to sleep indefinitely.", Thread.CurrentThread.Name); + #if DEBUG + Logger.VerboseDebug("Clearing Column Counters of: {0} non-written shards", columnCounters.Count); + #endif - Thread.Sleep(Timeout.Infinite); + columnCounters.Clear( ); - } catch (ThreadInterruptedException) { + //Then sleep until interupted again, and repeat +#if DEBUG + Logger.VerboseDebug("Thread '{0}' about to sleep indefinitely.", Thread.CurrentThread.Name); +#endif + Thread.Sleep(Timeout.Infinite); - Logger.VerboseDebug("Thread '{0}' interupted [awoken]", Thread.CurrentThread.Name); - goto wake; + } + catch (ThreadInterruptedException) + { - } catch (ThreadAbortException) { - Logger.VerboseDebug("Thread '{0}' aborted.", Thread.CurrentThread.Name); +#if DEBUG + Logger.VerboseDebug("Thread '{0}' interupted [awoken]", Thread.CurrentThread.Name); +#endif + goto wake; - } finally { - Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name); - } + } + catch (ThreadAbortException) + { +#if DEBUG + Logger.VerboseDebug("Thread '{0}' aborted.", Thread.CurrentThread.Name); +#endif + } + finally + { +#if DEBUG + Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name); +#endif + PersistPointsData(); + Write_PlainMetadata( ); + } } - private void UpdateStatus( uint totalUpdates, uint voidChunks, uint delta) + private void Snap() { - StatusData updateData = new StatusData(totalUpdates, voidChunks, delta, RunState.Run); - - this.ClientAPI.Event.PushEvent(AutomapStatusEventKey, updateData); + snapshotTake: +#if DEBUG + Logger.VerboseDebug("Snapshot started"); +#endif + try + { + snapshot.Take(); +#if DEBUG + Logger.VerboseDebug("Snapshot sleeping"); +#endif + CurrentState = CommandType.Run; + Thread.Sleep(Timeout.Infinite); + } + catch (ThreadInterruptedException) + { +#if DEBUG + Logger.VerboseDebug("Snapshot intertupted"); +#endif + goto snapshotTake; + } } - private void Prefill_POI_Designators( ) + private void UpdateStatus(uint totalUpdates, uint voidChunks, uint delta) { - this.POIs = new PointsOfInterest( ); - this.EOIs = new EntitiesOfInterest( ); - this.BlockID_Designators = new Dictionary( ); - this.Entity_Designators = new Dictionary( ); - this.RockIdCodes = Helpers.ArbitrarytBlockIdHunter(ClientAPI, new AssetLocation(GlobalConstants.DefaultDomain, "rock"), EnumBlockMaterial.Stone); + StatusData updateData = new StatusData(totalUpdates, voidChunks, delta, CommandType.Run); - //Add special marker types for BlockID's of "Interest", overwrite colour, and method - - Install_POI_Designators(DefaultDesignators.DefaultBlockDesignators(), DefaultDesignators.DefaultEntityDesignators()); + this.ClientAPI.Event.PushEvent(AutomapStatusEventKey, updateData); } - private void Install_POI_Designators(ICollection blockDesig, List entDesig) + private void Prefill_POI_Designators() { - Logger.VerboseDebug("Connecting {0} standard Block-Designators", blockDesig.Count); - foreach (var designator in blockDesig) { - var blockIDs = Helpers.ArbitrarytBlockIdHunter(ClientAPI, designator.Pattern, designator.Material); - if (blockIDs.Count > 0) { Logger.VerboseDebug("Designator {0} has {1} associated blockIDs", designator.ToString( ), blockIDs.Count); } - foreach (var entry in blockIDs) { - BlockID_Designators.Add(entry.Key, designator); - } - } - this.ChunkRenderer.BlockID_Designators = BlockID_Designators; - - - Logger.VerboseDebug("Connecting {0} standard Entity-Designators", entDesig.Count); - foreach (var designator in entDesig) { - //Get Variants first, from EntityTypes...better be populated! - var matched = ClientAPI.World.EntityTypes.FindAll(entp => entp.Code.BeginsWith(designator.Pattern.Domain, designator.Pattern.Path)); - - foreach (var match in matched) { - Logger.VerboseDebug("Linked Entity: {0} Designator: {1}", match.Code, designator); - this.Entity_Designators.Add(match.Code, designator); - } - - - //EntityProperties props = ClientAPI.World.GetEntityType(designator.Pattern); - } + this.BlockID_Designators = new Dictionary(); + this.Entity_Designators = new Dictionary(); + this.RockIdCodes = Helpers.ArbitrarytBlockIdHunter(ClientAPI, new AssetLocation(GlobalConstants.DefaultDomain, "rock-"), EnumBlockMaterial.Stone); + var airBlocksQuery = from airyBlock in ClientAPI.World.Blocks + where airyBlock.MatterState == EnumMatterState.Solid + where airyBlock.BlockMaterial == EnumBlockMaterial.Plant || airyBlock.BlockMaterial == EnumBlockMaterial.Leaves + where airyBlock.CollisionBoxes == null || airyBlock.CollisionBoxes.Length == 0 ||airyBlock.RainPermeable == true + select airyBlock; + //^^ 'Solid' phase - 'Plant' Blocks without any boundg box ? Except water... + this.AiryIdCodes = airBlocksQuery.ToDictionary(aBlk => aBlk.BlockId, aBlk => aBlk.Code.Path); + //Add special marker types for BlockID's of "Interest", overwrite colour, and method + Reload_POI_Designators(); } - - private void GenerateMapHTML( ) + private void Reload_POI_Designators() { - string mapFilename = Path.Combine(path, "Automap.html"); - - int TopNorth = chunkTopMetadata.North_mostChunk; - int TopSouth = chunkTopMetadata.South_mostChunk; - int TopEast = chunkTopMetadata.East_mostChunk; - int TopWest = chunkTopMetadata.West_mostChunk; - - using (StreamWriter outputText = new StreamWriter(File.Open(mapFilename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))) { - using (HtmlTextWriter tableWriter = new HtmlTextWriter(outputText)) { - tableWriter.BeginRender( ); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Html); - - tableWriter.RenderBeginTag(HtmlTextWriterTag.Head); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Title); - tableWriter.WriteEncodedText("Generated Automap"); - tableWriter.RenderEndTag( ); - //CSS style here - tableWriter.RenderBeginTag(HtmlTextWriterTag.Style); - tableWriter.Write(stylesFile.ToText( )); - tableWriter.RenderEndTag( );// - - //## JSON map-state data ###################### - tableWriter.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript"); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Script); - - tableWriter.Write("var available_images = ["); - - foreach (var shard in this.chunkTopMetadata) { - tableWriter.Write("{{X:{0},Y:{1} }}, ", shard.Location.X, shard.Location.Y); - } - - tableWriter.Write(" ];\n"); - - tableWriter.RenderEndTag( ); - - tableWriter.RenderEndTag( ); - - tableWriter.RenderBeginTag(HtmlTextWriterTag.Body); - tableWriter.RenderBeginTag(HtmlTextWriterTag.P); - tableWriter.WriteEncodedText($"Created {DateTimeOffset.UtcNow.ToString("u")}"); - tableWriter.RenderEndTag( ); - tableWriter.RenderBeginTag(HtmlTextWriterTag.P); - tableWriter.WriteEncodedText($"W:{TopWest}, E: {TopEast}, N:{TopNorth}, S:{TopSouth} "); - tableWriter.RenderEndTag( ); - tableWriter.WriteLine( ); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Table); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Caption); - tableWriter.WriteEncodedText($"Start: {startChunkColumn}, Seed: {ClientAPI.World.Seed}\n"); - tableWriter.RenderEndTag( ); - - //################ X-Axis ####################### - tableWriter.RenderBeginTag(HtmlTextWriterTag.Thead); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr); - - tableWriter.RenderBeginTag(HtmlTextWriterTag.Th); - tableWriter.Write("N, W"); - tableWriter.RenderEndTag( ); - - for (int xAxisT = TopWest; xAxisT <= TopEast; xAxisT++) { - tableWriter.RenderBeginTag(HtmlTextWriterTag.Th); - tableWriter.Write(xAxisT); - tableWriter.RenderEndTag( ); - } - - tableWriter.RenderBeginTag(HtmlTextWriterTag.Th); - tableWriter.Write("N, E"); - tableWriter.RenderEndTag( ); - - tableWriter.RenderEndTag( ); - tableWriter.RenderEndTag( ); - //###### ################################ - - //###### - Chunk rows & Y-axis cols - tableWriter.RenderBeginTag(HtmlTextWriterTag.Tbody); - - //######## for every vertical row - for (int yAxis = TopNorth; yAxis <= TopSouth; yAxis++) { - tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); - tableWriter.Write(yAxis);//legend: Y-axis - tableWriter.RenderEndTag( ); - - for (int xAxis = TopWest; xAxis <= TopEast; xAxis++) { - //###### #### for chunk shard - tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); - var colLoc = new Vec2i(xAxis, yAxis); - if (chunkTopMetadata.Contains( colLoc)){ - ColumnMeta meta = chunkTopMetadata[colLoc]; - //Tooltip first - tableWriter.AddAttribute(HtmlTextWriterAttribute.Class, "tooltip"); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Div); - - tableWriter.AddAttribute(HtmlTextWriterAttribute.Src, $"{xAxis}_{yAxis}.png"); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Img); - tableWriter.RenderEndTag( ); - // Tooltip text - tableWriter.AddAttribute(HtmlTextWriterAttribute.Class, "tooltiptext"); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Span); - - StringBuilder tooltipText = new StringBuilder( ); - tooltipText.Append($"{meta.Location.PrettyCoords(ClientAPI)} "); - tooltipText.Append($" Max-Height: {meta.YMax}, Temp: {meta.Temperature.ToString("F1")} " ); - tooltipText.Append($" Rainfall: {meta.Rainfall.ToString("F1")}, "); - tooltipText.Append($" Shrubs: {meta.ShrubDensity.ToString("F1")}, "); - tooltipText.Append($" Forest: {meta.ForestDensity.ToString("F1")}, "); - tooltipText.Append($" Fertility: {meta.Fertility.ToString("F1")}, "); - - if (meta.RockRatio != null) { - foreach (KeyValuePair blockID in meta.RockRatio) { - var block = ClientAPI.World.GetBlock(blockID.Key); - tooltipText.AppendFormat(" {0} × {1},\t", block.Code.GetName( ), meta.RockRatio[blockID.Key]); - } - } - - tableWriter.WriteEncodedText(tooltipText.ToString() ); - - tableWriter.RenderEndTag( );// - - - tableWriter.RenderEndTag( );// --tooltip enclosure - } - else { - tableWriter.Write("?"); - } - - tableWriter.RenderEndTag( ); - }//############ ########### - - tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); - tableWriter.Write(yAxis);//legend: Y-axis - tableWriter.RenderEndTag( ); - - tableWriter.RenderEndTag( ); - - } - tableWriter.RenderEndTag( ); + uint poisSetup =0, eoiSetup = 0; + foreach (var designator in configuration.BlockDesignators) + { + if (designator.Enabled == false) continue; + var blockIDs = Helpers.ArbitrarytBlockIdHunter(ClientAPI, designator.Pattern, designator.Material); + if (blockIDs.Count > 0) { Logger.VerboseDebug("Designator {0} has {1} associated blockIDs", designator.ToString(), blockIDs.Count); } + foreach (var entry in blockIDs) + { + BlockID_Designators.Add(entry.Key, designator); + poisSetup++; + } + } + this.ChunkRenderer.BlockID_Designators = BlockID_Designators; + Logger.VerboseDebug("Connected {0} IDs from {1} Block-Designators", poisSetup, configuration.BlockDesignators.Count ); - //################ X-Axis ####################### - tableWriter.RenderBeginTag(HtmlTextWriterTag.Tfoot); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); - tableWriter.Write("S, W"); - tableWriter.RenderEndTag( ); + foreach (var designator in configuration.EntityDesignators) + { + if (designator.Enabled == false) continue; + //Get Variants first, from EntityTypes...better be populated! + var matched = ClientAPI.World.EntityTypes.FindAll(entp => entp.Code.BeginsWith(designator.Pattern.Domain, designator.Pattern.Path)); + + foreach (var match in matched) + { + Logger.VerboseDebug("Linked Entity: {0} Designator: {1}", match.Code, designator); + this.Entity_Designators.Add(match.Code, designator); + eoiSetup++; + } + } + Logger.VerboseDebug("Connected {0} IDs from {1} Entity-Designators", eoiSetup, configuration.EntityDesignators.Count); - for (int xAxisB = TopWest; xAxisB <= TopEast; xAxisB++) { - tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); - tableWriter.Write(xAxisB); - tableWriter.RenderEndTag( ); } - tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); - tableWriter.Write("S, E"); - tableWriter.RenderEndTag( ); - tableWriter.RenderEndTag( ); - tableWriter.RenderEndTag( ); - //###### ################################ + /// + /// Store Points/Entity of Interest + /// + private void PersistPointsData() + { + //POI and EOI raw dump files ~ WRITE em! + //var poiRawFile = File. + string poiPath = Path.Combine(path, poiFileName); + string eoiPath = Path.Combine(path, eoiFileName); - tableWriter.RenderEndTag( );// - - //############## POI list ##################### - tableWriter.RenderBeginTag(HtmlTextWriterTag.P); - tableWriter.WriteLine("Points of Interest"); - tableWriter.RenderEndTag( ); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Ul); - foreach (var poi in this.POIs) { - tableWriter.RenderBeginTag(HtmlTextWriterTag.Li); - tableWriter.WriteEncodedText(poi.Location.PrettyCoords(this.ClientAPI)+"\t"); - tableWriter.WriteEncodedText(poi.Notes+ "\t"); - tableWriter.WriteEncodedText(poi.Timestamp.ToString("u")); - tableWriter.RenderEndTag( ); - } - - foreach (var eoi in this.EOIs.PointsList) { - tableWriter.RenderBeginTag(HtmlTextWriterTag.Li); - tableWriter.WriteEncodedText(eoi.Location.PrettyCoords(this.ClientAPI)+ "\t"); - tableWriter.WriteEncodedText(eoi.Notes+ "\t"); - tableWriter.WriteEncodedText(eoi.Timestamp.ToString("u") ); - tableWriter.RenderEndTag( ); - } - - tableWriter.RenderEndTag( ); + if (this.POIs.Count > 0) + { + using (var poiFile = File.Open(poiPath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + Serializer.Serialize(poiFile, this.POIs); + poiFile.Flush(true); + } + } - + if (this.EOIs.Count > 0) + { + using (var eoiFile = File.Open(eoiPath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + Serializer.Serialize(eoiFile, this.EOIs); + eoiFile.Flush(true); + } + } + //Create Easy to Parse TSV file for tool/human use.... + string pointsTsvPath = Path.Combine(path, pointsTsvFileName); - tableWriter.RenderEndTag( );//### ### - - tableWriter.EndRender( ); - tableWriter.Flush( ); - } - outputText.Flush( ); - } + using (var tsvWriter = new StreamWriter(pointsTsvPath, false, Encoding.UTF8)) + { + tsvWriter.WriteLine("Name\tDescription\tLocation\tTime\tDestination\tEntity_UID"); + foreach (var point in this.POIs) + { + tsvWriter.Write(point.Name + "\t"); + var notes = point.Notes + .Replace('\n', '\x001f') + .Replace("\t", "\\t") + .Replace("\\", "\\\\"); + tsvWriter.Write(notes + "\t"); + tsvWriter.Write(point.Location.PrettyCoords(ClientAPI) + "\t"); + tsvWriter.Write(point.Timestamp.ToString("u") + "\t"); + tsvWriter.Write((point.Destination != null ? point.Destination.PrettyCoords(ClientAPI) : "---") +"\t"); + tsvWriter.Write("null\t"); + tsvWriter.WriteLine(); + } + foreach (var entity in this.EOIs) + { + tsvWriter.Write(entity.Name + "\t"); + var notes = entity.Notes + .Replace('\n', '\x001f') + .Replace("\t", "\\t") + .Replace("\\", "\\\\"); + tsvWriter.Write(notes + "\t"); + tsvWriter.Write(entity.Location.PrettyCoords(ClientAPI) + "\t"); + tsvWriter.Write(entity.Timestamp.ToString("u") + "\t"); + tsvWriter.Write("---\t"); + tsvWriter.Write(entity.EntityId.ToString("D")); + tsvWriter.WriteLine(); + } + tsvWriter.WriteLine(); + tsvWriter.Flush(); + } - Logger.VerboseDebug("Generated HTML map"); } - /// - /// Generates the JSON Metadata. (in MAP object format ) - /// - private void GenerateJSONMetadata( ) - { - string jsonFilename = Path.Combine(path, "Metadata.js"); + private void Write_PlainMetadata( ) + { + string metaPath = Path.Combine(path, plainMetadataFileName); - StreamWriter jsonWriter = new StreamWriter(jsonFilename, false, Encoding.UTF8); - using (jsonWriter) + using (var metaDataFile = File.Open(metaPath,FileMode.Create)) { + using (var mdWriter = new StreamWriter(metaDataFile, Encoding.ASCII)) { - jsonWriter.WriteLine("var worldSeedNum = {0};", ClientAPI.World.Seed); - jsonWriter.WriteLine("var genTime = new Date('{0}');", DateTimeOffset.UtcNow.ToString("O")); - jsonWriter.WriteLine("var chunkSize = {0};", chunkSize); - jsonWriter.WriteLine("var northMostChunk ={0};", chunkTopMetadata.North_mostChunk); - jsonWriter.WriteLine("var southMostChunk ={0};", chunkTopMetadata.South_mostChunk); - jsonWriter.WriteLine("var eastMostChunk ={0};", chunkTopMetadata.East_mostChunk); - jsonWriter.WriteLine("var westMostChunk ={0};", chunkTopMetadata.West_mostChunk); - //MAP object format - [key, value]: key is "x_y" - jsonWriter.Write("let shardsMetadata = new Map(["); - foreach (var shard in chunkTopMetadata) - { - jsonWriter.Write("['{0}_{1}',", shard.Location.X, shard.Location.Y); - jsonWriter.Write("{"); - jsonWriter.Write("ChunkAge = '{0}',", shard.ChunkAge);//World age - relative? or last edit ?? - jsonWriter.Write("Temperature = {0},", shard.Temperature.ToString("F1")); - jsonWriter.Write("YMax = {0},", shard.YMax); - jsonWriter.Write("Fertility = {0},", shard.Fertility.ToString("F1")); - jsonWriter.Write("ForestDensity = {0},", shard.ForestDensity.ToString("F1")); - jsonWriter.Write("Rainfall = {0},", shard.Rainfall.ToString("F1")); - jsonWriter.Write("ShrubDensity = {0},", shard.ShrubDensity.ToString("F1")); - jsonWriter.Write("AirBlocks = {0},", shard.AirBlocks); - jsonWriter.Write("NonAirBlocks = {0},", shard.NonAirBlocks); - //TODO: Heightmap - //TODO: Rock-ratio - jsonWriter.Write("}],"); - //TODO: POIs - } - jsonWriter.Write("]);\n\n"); - jsonWriter.Flush( ); + mdWriter.WriteLine("WorldSeed {0}", ClientAPI.World.Seed); + mdWriter.WriteLine("PlayerChunkCoords {0:D} {1:D}", startChunkColumn.X, startChunkColumn.Y); + mdWriter.WriteLine("DefaultSpawnPos {0:D} {1:D} {2:D}", ClientAPI.World.DefaultSpawnPosition.AsBlockPos.X,ClientAPI.World.DefaultSpawnPosition.AsBlockPos.Y,ClientAPI.World.DefaultSpawnPosition.AsBlockPos.Z); + mdWriter.WriteLine("ChunkSize {0}", chunkSize); + mdWriter.WriteLine("SeaLevel {0:D}", ClientAPI.World.SeaLevel); + mdWriter.WriteLine("WorldSize {0:D} {1:D} {2:D}", ClientAPI.World.BulkBlockAccessor.MapSizeX, ClientAPI.World.BulkBlockAccessor.MapSizeY,ClientAPI.World.BulkBlockAccessor.MapSizeZ); + mdWriter.WriteLine("RegionSize {0:D}", ClientAPI.World.BulkBlockAccessor.RegionSize); + mdWriter.WriteLine("AMVersion '{0}'", ClientAPI.Self().Info.Version); + mdWriter.WriteLine("PlayTime {0:F1}", ClientAPI.InWorldEllapsedMilliseconds / 1000); + mdWriter.WriteLine("GameDate {0}", ClientAPI.World.Calendar.PrettyDate()); + mdWriter.WriteLine("Chunks {0:D}", chunkTopMetadata.Count); + mdWriter.WriteLine("Chunks Updated {0:D}", updatedChunksTotal); + mdWriter.WriteLine("Null Chunks {0:D}", nullChunkCount); + mdWriter.Flush( ); + } } - } + - - private ColumnMeta CreateColumnMetadata(KeyValuePair mostActiveCol, IMapChunk mapChunk) + private ColumnMeta CreateColumnMetadata(KeyValuePair mostActiveCol, IMapChunk mapChunk) { - ColumnMeta data = new ColumnMeta(mostActiveCol.Key.Copy(), chunkSize); - BlockPos equivBP = new BlockPos(mostActiveCol.Key.X * chunkSize, - mapChunk.YMax, - mostActiveCol.Key.Y * chunkSize); + ColumnMeta data = new ColumnMeta(mostActiveCol.Key.Copy(), ClientAPI, (byte) chunkSize, (ClientAPI.World.BlockAccessor.MapSizeY / chunkSize)); + BlockPos equivBP = new BlockPos(mostActiveCol.Key.X * chunkSize, + mapChunk.YMax, + mostActiveCol.Key.Y * chunkSize); - var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP); - data.UpdateFieldsFrom(climate, mapChunk,TimeSpan.FromHours(ClientAPI.World.Calendar.TotalHours)); + var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP); + data.UpdateFieldsFrom(climate, mapChunk, TimeSpan.FromHours(ClientAPI.World.Calendar.TotalHours)); - return data; + return data; } /// /// Reload chunk bounds from chunk shards /// /// The metadata. - private void Reload_Metadata( ) - { - var worldmapDir = new DirectoryInfo(path); - - if (worldmapDir.Exists) { + private void Reload_Metadata() + { + var shardsDir = new DirectoryInfo( Path.Combine(path, _chunkPath) ); - var files = worldmapDir.GetFiles(chunkFile_filter); + if (!shardsDir.Exists) + { + #if DEBUG + Logger.VerboseDebug("Could not open world map (shards) directory"); + #endif + return; + } + var shardFiles = shardsDir.GetFiles(chunkFile_filter); - if (files.Length > 0) { - #if DEBUG - Logger.VerboseDebug("{0} Existing world chunk shards", files.Length); - #endif + if (shardFiles.Length > 0) + { + #if DEBUG + Logger.VerboseDebug("Metadata reloading from {0} shards", shardFiles.Length); + #endif - + foreach (var shardFile in shardFiles) + { - foreach (var shardFile in files) { + if (shardFile.Length < 1024) continue; + var result = chunkShardRegex.Match(shardFile.Name); + if (!result.Success) continue; + + int X_chunk_pos = int.Parse(result.Groups["X"].Value); + int Z_chunk_pos = int.Parse(result.Groups["Z"].Value); + + try + { + using (var fileStream = shardFile.OpenRead()) + { + + PngReader pngRead = new PngReader(fileStream); + pngRead.ReadSkippingAllRows(); + pngRead.End(); + //Parse PNG chunks for METADATA in shard + PngMetadataChunk metadataFromPng = pngRead.GetChunksList().GetById1(PngMetadataChunk.ID) as PngMetadataChunk; + var column = metadataFromPng.ChunkMetadata; + if (column.PrettyLocation == null) + column = column.Reload(ClientAPI); + chunkTopMetadata.Add(column); + } + + } + catch (PngjException someEx) + { + Logger.Error("PNG Corruption file '{0}' - Reason: {1}", shardFile.Name, someEx); + continue; + } + catch (ProtoException protoEx) + { + Logger.Error("ProtoBuf invalid! file:'{0}' - Reason: {1}", shardFile.Name, protoEx); + continue; + } + } + } - if (shardFile.Length < 512) continue; - var result = chunkShardRegex.Match(shardFile.Name); - if (result.Success) { - int X_chunk_pos = int.Parse(result.Groups["X"].Value ); - int Z_chunk_pos = int.Parse(result.Groups["Z"].Value ); - - //Parse PNG chunks for METADATA in shard - using (var fileStream = shardFile.OpenRead( )) - { - //TODO: Add corrupted PNG Exception handing HERE ! - PngReader pngRead = new PngReader(fileStream ); - pngRead.ReadSkippingAllRows( ); - pngRead.End( ); + //POI and EOI raw dump files ~ reload em! + //var poiRawFile = File. + string poiPath = Path.Combine(path, poiFileName); + string eoiPath = Path.Combine(path, eoiFileName); - PngMetadataChunk metadataFromPng = pngRead.GetChunksList( ).GetById1(PngMetadataChunk.ID) as PngMetadataChunk; + if (File.Exists(poiPath)) + { + using (var poiFile = File.OpenRead(poiPath)) + { + this.POIs = Serializer.Deserialize(poiFile); + Logger.VerboseDebug("Reloaded {0} POIs from file.", this.POIs.Count); + } + } - chunkTopMetadata.Add(metadataFromPng.ChunkMetadata); - } - - } - } + if (File.Exists(eoiPath)) + { + using (var eoiFile = File.OpenRead(eoiPath)) + { + this.EOIs = Serializer.Deserialize(eoiFile); + Logger.VerboseDebug("Reloaded {0} EOIs from file.", this.EOIs.Count); + } + } } - } - else { - #if DEBUG - Logger.VerboseDebug("Could not open world map directory"); - #endif - } - - - } - private PngWriter SetupPngImage(Vec2i coord, ColumnMeta metadata) - { - ImageInfo imageInf = new ImageInfo(chunkSize, chunkSize, 8, false); - - string filename = $"{coord.X}_{coord.Y}.png"; - filename = Path.Combine(path, filename); - - PngWriter pngWriter = FileHelper.CreatePngWriter(filename, imageInf, true); - PngMetadata meta = pngWriter.GetMetadata( ); - meta.SetTimeNow( ); - meta.SetText("Chunk_X", coord.X.ToString("D")); - meta.SetText("Chunk_Y", coord.Y.ToString("D")); - //Setup specialized meta-data PNG chunks here... - PngMetadataChunk pngChunkMeta = new PngMetadataChunk(pngWriter.ImgInfo); - pngChunkMeta.ChunkMetadata = metadata; - pngWriter.GetChunksList( ).Queue(pngChunkMeta); - - return pngWriter; - } /// - /// Does the heavy lifting of Scanning columns of chunks - creates Heightmap and Processes POIs, Entities, and stats... + /// Does the heavy lifting of Scanning columns of chunks - scans for BlockEntity, creates Heightmap and stats... /// /// Chunk Coordinate /// Map chunk. /// Chunk metadata - private void ProcessChunkBlocks(Vec2i key, IMapChunk mapChunk, ColumnMeta chunkMeta) + private void ProcessChunkBlocks(Vec2i key, IMapChunk mapChunk, ref ColumnMeta chunkMeta) { + int targetChunkY = mapChunk.YMax / chunkSize;//Surface ish... + byte chunkTally = 0; - int targetChunkY = mapChunk.YMax / chunkSize;//Surface ... - for (; targetChunkY > 0; targetChunkY--) { - WorldChunk chunkData = ClientAPI.World.BlockAccessor.GetChunk(key.X, targetChunkY, key.Y) as WorldChunk; - - if (chunkData == null || chunkData.BlockEntities == null) { #if DEBUG - Logger.VerboseDebug("Chunk null or empty X{0} Y{1} Z{2}", key.X,targetChunkY,key.Y); + Logger.VerboseDebug("Start col @ X{0} Y{1} Z{2} !", key.X, targetChunkY, key.Y); #endif - continue; - } - /*************** Chunk Entities Scanning *********************/ - if (chunkData.BlockEntities != null && chunkData.BlockEntities.Length > 0) { - #if DEBUG - Logger.VerboseDebug("Surface@ {0} = BlockEntities: {1}", key, chunkData.BlockEntities.Length); - #endif + chunkMeta.ResetMetadata(ClientAPI.World.BlockAccessor.MapSizeY); - foreach (var blockEnt in chunkData.BlockEntities) { - - if (blockEnt != null && blockEnt.Block != null && BlockID_Designators.ContainsKey(blockEnt.Block.BlockId)) + for (; targetChunkY > 0; targetChunkY--) { - var designator = BlockID_Designators[blockEnt.Block.BlockId]; - designator.SpecialAction(ClientAPI, POIs, blockEnt.Pos.Copy( ), blockEnt.Block); - } - } - - } - /********************* Chunk/Column BLOCKs scanning ****************/ - //Heightmap, Stats, block tally - chunkData.Unpack( ); - - int X_index, Y_index, Z_index; - X_index = Y_index = Z_index = 0; - - do { - do { - do { - /* Encode packed indicie - (y * chunksize + z) * chunksize + x - */ - var indicie = Helpers.ChunkBlockIndicie16(X_index, Y_index, Z_index); - int aBlockId = chunkData.Blocks[indicie]; - - if (aBlockId == 0) {//Air - chunkMeta.AirBlocks++; - continue; - } + WorldChunk worldChunk = ClientAPI.World.BlockAccessor.GetChunk(key.X, targetChunkY, key.Y) as WorldChunk; - if (RockIdCodes.ContainsKey(aBlockId)) { - if (chunkMeta.RockRatio.ContainsKey(aBlockId)) { chunkMeta.RockRatio[aBlockId]++; } else { chunkMeta.RockRatio.Add(aBlockId, 1); } - } - - chunkMeta.NonAirBlocks++; + if (worldChunk == null || worldChunk.BlockEntities == null) + { + #if DEBUG + Logger.VerboseDebug("WORLD chunk: null or empty X{0} Y{1} Z{2} !", key.X, targetChunkY, key.Y); + #endif + nullChunkCount++; + continue; + } - //Heightmap - if (chunkMeta.HeightMap[X_index, Z_index] == 0) - { chunkMeta.HeightMap[X_index, Z_index] = ( ushort )(Y_index + (targetChunkY * chunkSize)); } + if (worldChunk.IsPacked()) + { + #if DEBUG + Logger.VerboseDebug("WORLD chunk: Compressed: X{0} Y{1} Z{2}", key.X, targetChunkY, key.Y); + #endif + worldChunk.Unpack( );//RESEARCH: Thread Unsafe? + } - } - while (X_index++ < (chunkSize - 1)); - X_index = 0; - } - while (Z_index++ < (chunkSize - 1)); - Z_index = 0; - } - while (Y_index++ < (chunkSize - 1)); + /*************** Chunk Entities Scanning *********************/ + if (worldChunk.BlockEntities != null && worldChunk.BlockEntities.Count > 0) + { + #if DEBUG + Logger.VerboseDebug("Scan pos.({0}) for BlockEntities# {1}", key, worldChunk.BlockEntities.Count); + #endif + + foreach (var blockEnt in worldChunk.BlockEntities) + { + if (blockEnt.Key != null && blockEnt.Value != null && blockEnt.Value.Block != null && BlockID_Designators.ContainsKey(blockEnt.Value.Block.BlockId)) + { + var designator = BlockID_Designators[blockEnt.Value.Block.BlockId]; + designator?.SpecialAction(ClientAPI, POIs, blockEnt.Value.Pos.Copy(), blockEnt.Value.Block); + } + } + } - } - } + /********************* Chunk/Column BLOCKs scanning ****************/ + //Heightmap, Stats, block tally - private void UpdateEntityMetadata( ) - { - Logger.Debug("Presently {0} Entities", ClientAPI.World.LoadedEntities.Count); - //Mabey scan only for 'new' entities by tracking ID in set? - foreach (var loadedEntity in ClientAPI.World.LoadedEntities.ToList()) { - - #if DEBUG - //Logger.VerboseDebug($"ENTITY: ({loadedEntity.Value.Code}) = #{loadedEntity.Value.EntityId} {loadedEntity.Value.State} {loadedEntity.Value.LocalPos} <<<<<<<<<<<<"); - #endif + int X_index, Y_index, Z_index; - var dMatch = Entity_Designators.SingleOrDefault(se => se.Key.Equals(loadedEntity.Value.Code)); - if (dMatch.Value != null) { - dMatch.Value.SpecialAction(ClientAPI, this.EOIs, loadedEntity.Value.LocalPos.AsBlockPos.Copy( ), loadedEntity.Value); - } + //First Chance fail-safe; + if (worldChunk.Blocks == null || worldChunk.Blocks.Length <= 0) { + #if DEBUG + Logger.VerboseDebug("WORLD chunk; Missing block DATA⁈ X{0} Y{1} Z{2} ⁈", key.X, targetChunkY, key.Y); + #endif + nullChunkCount++; + continue; + } + chunkMeta.ColumnPresense[targetChunkY] = true; + chunkTally++; + for (Y_index = 0; Y_index < chunkSize; Y_index++) + { + for (Z_index = 0; Z_index < chunkSize; Z_index++) + { + for (X_index = 0; X_index < chunkSize; X_index++) + { + var indicie = MapUtil.Index3d(X_index, Y_index, Z_index, chunkSize, chunkSize); + + //'Last' Chance fail-safe; + if (worldChunk.Blocks == null || worldChunk.Blocks.Length <= 0) { + #if DEBUG + Logger.VerboseDebug("Processing Block: Missing block DATA⁈ X{0} Y{1} Z{2} ⁈", X_index, Y_index, Z_index); + #endif + nullChunkCount++; + goto loop_bustout; + } + + int aBlockId = worldChunk.Blocks[indicie]; + + if (aBlockId == 0 || AiryIdCodes.ContainsKey(aBlockId)) {//Airy blocks,,, + chunkMeta.AirBlocks++; + continue; + } + + if (RockIdCodes.ContainsKey(aBlockId)) { + if (chunkMeta.RockRatio.ContainsKey(aBlockId)) + chunkMeta.RockRatio[aBlockId]++; + else + chunkMeta.RockRatio.Add(aBlockId, 1); + } + + chunkMeta.NonAirBlocks++; + + ushort localHeight = ( ushort )(Y_index + (targetChunkY * chunkSize)); + //Heightmap - Need to ignore Grass & Snow + if (localHeight > chunkMeta.HeightMap[X_index, Z_index]) + { + chunkMeta.HeightMap[X_index, Z_index] = localHeight; + if (localHeight > chunkMeta.YMax) chunkMeta.YMax = localHeight; + } + } + } + } + loop_bustout:; + } + #if DEBUG + Logger.VerboseDebug("COLUMN X{0} Z{1}: {2}, processed.", key.X , key.Y, chunkTally + 1); + #endif } + private void UpdateEntityMetadata() + { + #if DEBUG + Logger.Debug("Presently {0} Entities", ClientAPI.World.LoadedEntities.Count); + #endif + //Mabey scan only for 'new' entities by tracking ID in set? + foreach (var loadedEntity in ClientAPI.World.LoadedEntities.ToArray()) + { - } + #if DEBUG + //Logger.VerboseDebug($"ENTITY: ({loadedEntity.Value.Code}) = #{loadedEntity.Value.EntityId} {loadedEntity.Value.State} {loadedEntity.Value.LocalPos} <<<<<<<<<<<<"); + #endif + var dMatch = Entity_Designators.SingleOrDefault(se => se.Key.Equals(loadedEntity.Value.Code)); + if (dMatch.Value != null) + { + dMatch.Value.SpecialAction(ClientAPI, this.EOIs, loadedEntity.Value.Pos.AsBlockPos.Copy(), loadedEntity.Value); + } - private void CommandListener(string eventName, ref EnumHandling handling, IAttribute data) - { - Logger.VerboseDebug("MsgBus RX: AutomapCommandMsg: {0}", data.ToJsonToken() ); + } - CommandData cmdData = data as CommandData; + } - if (CurrentState != RunState.Snapshot) { - switch (cmdData.State) { - case RunState.Run: - CurrentState = cmdData.State; - AwakenCartographer(0.0f); - break; + private void AddNote(string notation) + { + var playerNodePoi = new PointOfInterest() + { + Name = "Note", + Location = ClientAPI.World.Player.Entity.Pos.AsBlockPos.Copy(), + Notes = notation, + Timestamp = DateTime.UtcNow, + }; - case RunState.Stop: - CurrentState = cmdData.State; - break; + this.POIs.AddReplace(playerNodePoi); + } - case RunState.Snapshot: - CurrentState = RunState.Stop; - //Snapshot starts a second thread/process... - break; - } - } + private void CommandListener(string eventName, ref EnumHandling handling, IAttribute data) + { + //Logger.VerboseDebug("MsgBus RX: AutomapCommandMsg: {0}", data.ToJsonToken()); - if (CurrentState != cmdData.State) { - CurrentState = cmdData.State; - AwakenCartographer(0.0f); + CommandData cmdData = data as CommandData; - #if DEBUG - ClientAPI.TriggerChatMessage($"Automap commanded to: {cmdData.State} "); - #endif - } - - } + switch (cmdData.State) + { + case CommandType.Run: + case CommandType.Stop: + case CommandType.Snapshot: + if (CurrentState != cmdData.State) + { + CurrentState = cmdData.State; + AwakenCartographer(0.0f); + } + break; + + case CommandType.Notation: + //Add to POI list where player location + AddNote(cmdData.Notation); + break; + } + ClientAPI.TriggerChatMessage($"Automap commanded to: {cmdData.State} "); - #endregion + } +#endregion + private AChunkRenderer InstantiateChosenRenderer(string rendererName ) + { + Logger.VerboseDebug("Using '{0}' style Shard Renderer", rendererName); + switch (rendererName) + { + case StandardRenderer.Name: + return new StandardRenderer(ClientAPI, Logger, this.configuration.SeasonalColors); + + case AlternateRenderer.Name: + return new AlternateRenderer(ClientAPI, Logger, this.configuration.SeasonalColors); + + case FlatRenderer.Name: + return new FlatRenderer(ClientAPI, Logger, this.configuration.SeasonalColors); + default: + throw new ArgumentOutOfRangeException("rendererName",rendererName,"That value isn't supported or known..."); + } + return null; + } } -} \ No newline at end of file +}