From ae9502a7c99f2b665149095a255bbfaf85ca35a7 Mon Sep 17 00:00:00 2001 From: melchior Date: Sat, 7 Dec 2019 17:45:55 -0500 Subject: [PATCH] W.I.P. #3 This Coordinate system...tricky! --- Automap/Automap.csproj | 8 + Automap/AutomapMod.cs | 282 +------------- Automap/Automap_Internals.cs | 447 +++++++++++++++++++++++ Automap/Helpers.cs | 1 - Automap/assets/automap/config/automap_format.css | 44 +++ 5 files changed, 501 insertions(+), 281 deletions(-) create mode 100644 Automap/Automap_Internals.cs create mode 100644 Automap/assets/automap/config/automap_format.css diff --git a/Automap/Automap.csproj b/Automap/Automap.csproj index e5e023d..bc26e1d 100644 --- a/Automap/Automap.csproj +++ b/Automap/Automap.csproj @@ -61,6 +61,7 @@ False + @@ -68,15 +69,22 @@ + + + + Always + + Always + \ No newline at end of file diff --git a/Automap/AutomapMod.cs b/Automap/AutomapMod.cs index dd5e950..ec224ef 100644 --- a/Automap/AutomapMod.cs +++ b/Automap/AutomapMod.cs @@ -1,42 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Threading; -using System.Drawing; -using System.Drawing.Imaging; -using System.Linq; -using System.Collections.Concurrent; - + using Vintagestory.API.Client; using Vintagestory.API.Common; -using Vintagestory.API.Common.Entities; -using Vintagestory.API.Config; -using Vintagestory.API.MathTools; -using Vintagestory.API.Server; -using Vintagestory.GameContent; -using Vintagestory.API.Datastructures; namespace Automap { - public class AutomapMod : ModSystem + public partial class AutomapMod : ModSystem { private ICoreAPI API { get; set; } private ICoreClientAPI ClientAPI { get; set; } private ILogger Logger { get; set; } - private Thread cartographer_thread; - - private const string _mapPath = @"Maps"; - private const string _chunkPath = @"Chunks"; - private ConcurrentDictionary columnCounter = new ConcurrentDictionary(); - private HashSet knownChunkTops = new HashSet(); - private Vec2i startPosition; - private List POIs; - private Dictionary BlockID_Designators; public override bool ShouldLoad(EnumAppSide forSide) { @@ -65,261 +41,7 @@ namespace Automap - #region Internals - private void StartAutomap( ) - { - Prefill_POI_Designators( ); - startPosition = new Vec2i((ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.X / ClientAPI.World.BlockAccessor.ChunkSize), (ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Z/ ClientAPI.World.BlockAccessor.ChunkSize)); - Logger.Notification("AUTOMAP Start {0}", startPosition); - ClientAPI.Event.ChunkDirty += ChunkAChanging; - - cartographer_thread = new Thread(Cartographer); - cartographer_thread.Name = "Cartographer"; - cartographer_thread.Priority = ThreadPriority.Lowest; - cartographer_thread.IsBackground = true; - - ClientAPI.Event.RegisterGameTickListener(AwakenCartographer, 6000); - } - - private void AwakenCartographer(float delayed) - { - - if (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( ); - } - - ClientAPI.TriggerChatMessage($"Automap processed {knownChunkTops.Count} chunks."); - } - - } - - - private void Cartographer( ) - { - wake: - Logger.VerboseDebug("Cartographer thread awoken"); - - try { - uint ejectedItem = 0; - - while (columnCounter.Count > 0) - { - var mostActiveCol = columnCounter.OrderByDescending(kvp => kvp.Value).First(); - var mapChunk = ClientAPI.World.BlockAccessor.GetMapChunk(mostActiveCol.Key); - - - if (mapChunk == null) { - Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!",mostActiveCol.Key); - - columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem); - continue; - } - - string filename = $"{mostActiveCol.Key.X}_{mostActiveCol.Key.Y}.png"; - string path = ClientAPI.GetOrCreateDataPath(_mapPath); - path = ClientAPI.GetOrCreateDataPath(Path.Combine(path, "World_" + ClientAPI.World.Seed)); - - filename = Path.Combine(path, filename); - - uint pixels = 0; - var chkImg = GenerateChunkImage(mostActiveCol.Key, mapChunk, out pixels); - if (pixels > 0) { - chkImg.Save(filename, ImageFormat.Png); - #if DEBUG - Logger.VerboseDebug("Wrote chunk shard: ({0}) - Edits#:{1}, Pixels#:{2}", mostActiveCol.Key, mostActiveCol.Value, pixels); - #endif - - knownChunkTops.Add(mostActiveCol.Key); - columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem); - } - else { - columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem); - Logger.VerboseDebug("Un-painted chunk: ({0}) ", mostActiveCol.Key); - } - - } - - - - //Then sleep until interupted again, and repeat - - Logger.VerboseDebug("Thread '{0}' about to sleep indefinitely.", Thread.CurrentThread.Name); - - Thread.Sleep(Timeout.Infinite); - - } catch (ThreadInterruptedException) { - - Logger.VerboseDebug("Thread '{0}' interupted [awoken]", Thread.CurrentThread.Name); - goto wake; - - } catch (ThreadAbortException) { - Logger.VerboseDebug("Thread '{0}' aborted.", Thread.CurrentThread.Name); - - } finally { - Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name); - } - } - #endregion - - - - private void ChunkAChanging(Vec3i chunkCoord, IWorldChunk chunk, EnumChunkDirtyReason reason) - { - //Logger.VerboseDebug($"Change: @({chunkCoord}) R: {reason}"); - Vec2i topPosition = new Vec2i(chunkCoord.X, chunkCoord.Z); - - columnCounter.AddOrUpdate(topPosition, 1, (key, colAct) => colAct + 1); - } - - private void Prefill_POI_Designators( ) - { - this.POIs = new List( ); - var roadIDs = Helpers.ArbitrarytBlockIdHunter(ClientAPI, new AssetLocation("game","stonepath"), EnumBlockMaterial.Gravel); - - //Add special marker types for BlockID's of "Interest", plus a special overwrite colour for them - this.BlockID_Designators = new Dictionary( ); - - foreach (var entry in roadIDs) { - BlockID_Designators.Add (entry.Key, new Designator - ( - Color.Yellow - )); - } - - - - } - - - - #region COPYPASTA - //A slightly re-written; ChunkMapLayer :: public int[] GenerateChunkImage(Vec2i chunkPos, IMapChunk mc) - internal Bitmap GenerateChunkImage(Vec2i chunkPos, IMapChunk mc, out uint pixelCount) - { - pixelCount = 0; - BlockPos tmpPos = new BlockPos( ); - Vec2i localpos = new Vec2i( ); - int chunkSize = ClientAPI.World.BlockAccessor.ChunkSize; - var chunksColumn = new IWorldChunk[ClientAPI.World.BlockAccessor.MapSizeY / chunkSize]; - Bitmap chunkImage = new Bitmap(chunkSize, chunkSize, PixelFormat.Format24bppRgb ); - int topChunkY = mc.YMax / chunkSize;//Heywaitaminute -- this isn't a highest FEATURE, if Rainmap isn't accurate! - //Metadata of DateTime chunk was edited, chunk coords.,world-seed? Y-Max feature height - //Grab a chunk COLUMN... Topmost Y down... - for (int chunkY = 0; chunkY < topChunkY; chunkY++) { - chunksColumn[chunkY] = ClientAPI.World.BlockAccessor.GetChunk(chunkPos.X, chunkY, chunkPos.Y); - //What to do if chunk is a void? invalid? - } - - // Prefetch map chunks, in pattern - IMapChunk[ ] mapChunks = new IMapChunk[ ] - { - ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y - 1), - ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y), - ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X, chunkPos.Y - 1) - }; - - - for (int posIndex = 0; posIndex < (chunkSize * chunkSize); posIndex++) { - int mapY = mc.RainHeightMap[posIndex]; - int localChunkY = mapY / chunkSize; - if (localChunkY >= (chunksColumn.Length ) ) continue;//Out of range! - - MapUtil.PosInt2d(posIndex, chunkSize, localpos); - int localX = localpos.X; - int localZ = localpos.Y; - - float b = 1; - int leftTop, rightTop, leftBot; - - IMapChunk leftTopMapChunk = mc; - IMapChunk rightTopMapChunk = mc; - IMapChunk leftBotMapChunk = mc; - - int topX = localX - 1; - int botX = localX; - int leftZ = localZ - 1; - int rightZ = localZ; - - if (topX < 0 && leftZ < 0) { - leftTopMapChunk = mapChunks[0]; - rightTopMapChunk = mapChunks[1]; - leftBotMapChunk = mapChunks[2]; - } - else { - if (topX < 0) { - leftTopMapChunk = mapChunks[1]; - rightTopMapChunk = mapChunks[1]; - } - if (leftZ < 0) { - leftTopMapChunk = mapChunks[2]; - leftBotMapChunk = mapChunks[2]; - } - } - - topX = GameMath.Mod(topX, chunkSize); - leftZ = GameMath.Mod(leftZ, chunkSize); - - leftTop = leftTopMapChunk == null ? 0 : Math.Sign(mapY - leftTopMapChunk.RainHeightMap[leftZ * chunkSize + topX]); - rightTop = rightTopMapChunk == null ? 0 : Math.Sign(mapY - rightTopMapChunk.RainHeightMap[rightZ * chunkSize + topX]); - leftBot = leftBotMapChunk == null ? 0 : Math.Sign(mapY - leftBotMapChunk.RainHeightMap[leftZ * chunkSize + botX]); - - float slopeness = (leftTop + rightTop + leftBot); - - if (slopeness > 0) b = 1.2f; - if (slopeness < 0) b = 0.8f; - - b -= 0.15f; // Map seems overally a bit too bright - //b = 1; - if (chunksColumn[localChunkY] == null) { - - continue; - } - - chunksColumn[localChunkY].Unpack( ); - int blockId = chunksColumn[localChunkY].Blocks[MapUtil.Index3d(localpos.X, mapY % chunkSize, localpos.Y, chunkSize, chunkSize)]; - - Block block = ClientAPI.World.Blocks[blockId]; - - tmpPos.Set(chunkSize * chunkPos.X + localpos.X, mapY, chunkSize * chunkPos.Y + localpos.Y); - - int avgCol = block.GetColor(ClientAPI, tmpPos); - int rndCol = block.GetRandomColor(ClientAPI, tmpPos, BlockFacing.UP); - //Merge color? - int col = ColorUtil.ColorOverlay(avgCol, rndCol, 0.125f); - var packedFormat = ColorUtil.ColorMultiply3Clamped(col, b) | 255 << 24;//Is the Struct, truly so undesirable? - - Color pixelColor = Color.FromArgb(ColorUtil.ColorR(packedFormat), ColorUtil.ColorG(packedFormat), ColorUtil.ColorB(packedFormat)); - - //============ POI Population ================= - if (BlockID_Designators.ContainsKey(blockId)) { - var desig = BlockID_Designators[blockId]; - pixelColor = desig.OverwriteColor; - - if (desig.SpecialAction != null) { - desig.SpecialAction.Invoke(tmpPos, block); - } - } - - chunkImage.SetPixel(localX,localZ, pixelColor); - pixelCount++; - } - - - return chunkImage; - } - #endregion } } diff --git a/Automap/Automap_Internals.cs b/Automap/Automap_Internals.cs new file mode 100644 index 0000000..486a225 --- /dev/null +++ b/Automap/Automap_Internals.cs @@ -0,0 +1,447 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Threading; + +using System.Web.UI; + +using Vintagestory.API.Common; +using Vintagestory.API.MathTools; + + + +namespace Automap +{ + public partial class AutomapMod + { + private Thread cartographer_thread; + + private const string _mapPath = @"Maps"; + private const string _chunkPath = @"Chunks"; + private const string _domain = @"automap"; + + private ConcurrentDictionary columnCounter = new ConcurrentDictionary( ); + private HashSet knownChunkTops = new HashSet( ); + + private List POIs; + private Dictionary BlockID_Designators; + + private int North_mostChunk; + private int East_mostChunk; + private int West_mostChunk; + private int South_mostChunk; + private Vec2i startChunkColumn; + private uint lastUpdate; + + private string path; + private IAsset stylesFile; + + #region Internals + private void StartAutomap( ) + { + path = ClientAPI.GetOrCreateDataPath(_mapPath); + path = ClientAPI.GetOrCreateDataPath(Path.Combine(path, "World_" + ClientAPI.World.Seed)); + + stylesFile = ClientAPI.World.AssetManager.Get(new AssetLocation(_domain, "config/automap_format.css")); + Logger.VerboseDebug("CSS loaded: {0} size: {1}",stylesFile.IsLoaded() ,stylesFile.ToText( ).Length); + + + Prefill_POI_Designators( ); + startChunkColumn = new Vec2i((ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.X / ClientAPI.World.BlockAccessor.ChunkSize), (ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Z / ClientAPI.World.BlockAccessor.ChunkSize)); + North_mostChunk = startChunkColumn.Y; + South_mostChunk = startChunkColumn.Y; + East_mostChunk = startChunkColumn.X; + West_mostChunk = startChunkColumn.X; + + Logger.Notification("AUTOMAP Start {0}", startChunkColumn); + + ClientAPI.Event.ChunkDirty += ChunkAChanging; + + cartographer_thread = new Thread(Cartographer); + cartographer_thread.Name = "Cartographer"; + cartographer_thread.Priority = ThreadPriority.Lowest; + cartographer_thread.IsBackground = true; + + 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); + } + + private void AwakenCartographer(float delayed) + { + + if (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( ); + } + ClientAPI.TriggerChatMessage($"Automap {lastUpdate} changes - MAX (N:{North_mostChunk},S:{South_mostChunk},E:{East_mostChunk}, W:{West_mostChunk} - TOTAL: {knownChunkTops.Count})"); + } + + } + + + private void Cartographer( ) + { + wake: + Logger.VerboseDebug("Cartographer thread awoken"); + + try { + uint ejectedItem = 0; + uint updatedChunks = 0; + + while (columnCounter.Count > 0) { + var mostActiveCol = columnCounter.OrderByDescending(kvp => kvp.Value).First( ); + var mapChunk = ClientAPI.World.BlockAccessor.GetMapChunk(mostActiveCol.Key); + + + if (mapChunk == null) { + Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!", mostActiveCol.Key); + + columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem); + continue; + } + + string filename = $"{mostActiveCol.Key.X}_{mostActiveCol.Key.Y}.png"; + filename = Path.Combine(path, filename); + + uint pixels = 0; + var chkImg = GenerateChunkImage(mostActiveCol.Key, mapChunk, out pixels); + + if (pixels > 0) { + chkImg.Save(filename, ImageFormat.Png); + #if DEBUG + Logger.VerboseDebug("Wrote chunk shard: ({0}) - Edits#:{1}, Pixels#:{2}", mostActiveCol.Key, mostActiveCol.Value, pixels); + #endif + updatedChunks++; + knownChunkTops.Add(mostActiveCol.Key); + columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem); + } + else { + columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem); + Logger.VerboseDebug("Un-painted chunk: ({0}) ", mostActiveCol.Key); + } + + } + + if (updatedChunks > 0) { + lastUpdate = updatedChunks; + GenerateMapHTML( ); + updatedChunks = 0; + } + + //Then sleep until interupted again, and repeat + + Logger.VerboseDebug("Thread '{0}' about to sleep indefinitely.", Thread.CurrentThread.Name); + + Thread.Sleep(Timeout.Infinite); + + } catch (ThreadInterruptedException) { + + Logger.VerboseDebug("Thread '{0}' interupted [awoken]", Thread.CurrentThread.Name); + goto wake; + + } catch (ThreadAbortException) { + Logger.VerboseDebug("Thread '{0}' aborted.", Thread.CurrentThread.Name); + + } finally { + Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name); + } + } + #endregion + + + + + + private void Prefill_POI_Designators( ) + { + this.POIs = new List( ); + this.BlockID_Designators = new Dictionary( ); + + //Add special marker types for BlockID's of "Interest", plus a special overwrite colour for them + + var roadIDs = Helpers.ArbitrarytBlockIdHunter(ClientAPI, new AssetLocation("game", "stonepath"), EnumBlockMaterial.Gravel); + var roadDesignator = new Designator + ( + Color.Yellow + ); + + foreach (var entry in roadIDs) { + BlockID_Designators.Add(entry.Key, roadDesignator); + } + + + + } + + + + #region COPYPASTA + //TODO: rewrite - with alternate algo. + //A slightly re-written; ChunkMapLayer :: public int[] GenerateChunkImage(Vec2i chunkPos, IMapChunk mc) + internal Bitmap GenerateChunkImage(Vec2i chunkPos, IMapChunk mc, out uint pixelCount) + { + pixelCount = 0; + BlockPos tmpPos = new BlockPos( ); + Vec2i localpos = new Vec2i( ); + int chunkSize = ClientAPI.World.BlockAccessor.ChunkSize; + var chunksColumn = new IWorldChunk[ClientAPI.World.BlockAccessor.MapSizeY / chunkSize]; + Bitmap chunkImage = new Bitmap(chunkSize, chunkSize, PixelFormat.Format24bppRgb); + int topChunkY = mc.YMax / chunkSize;//Heywaitaminute -- this isn't a highest FEATURE, if Rainmap isn't accurate! + //Metadata of DateTime chunk was edited, chunk coords.,world-seed? Y-Max feature height + //Grab a chunk COLUMN... Topmost Y down... + for (int chunkY = 0; chunkY <= topChunkY; chunkY++) { + chunksColumn[chunkY] = ClientAPI.World.BlockAccessor.GetChunk(chunkPos.X, chunkY, chunkPos.Y); + //What to do if chunk is a void? invalid? + } + + // Prefetch map chunks, in pattern + IMapChunk[ ] mapChunks = new IMapChunk[ ] + { + ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y - 1), + ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y), + ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X, chunkPos.Y - 1) + }; + + + for (int posIndex = 0; posIndex < (chunkSize * chunkSize); posIndex++) { + int mapY = mc.RainHeightMap[posIndex]; + int localChunkY = mapY / chunkSize; + if (localChunkY >= (chunksColumn.Length)) continue;//Out of range! + + MapUtil.PosInt2d(posIndex, chunkSize, localpos); + int localX = localpos.X; + int localZ = localpos.Y; + + float b = 1; + int leftTop, rightTop, leftBot; + + IMapChunk leftTopMapChunk = mc; + IMapChunk rightTopMapChunk = mc; + IMapChunk leftBotMapChunk = mc; + + int topX = localX - 1; + int botX = localX; + int leftZ = localZ - 1; + int rightZ = localZ; + + if (topX < 0 && leftZ < 0) { + leftTopMapChunk = mapChunks[0]; + rightTopMapChunk = mapChunks[1]; + leftBotMapChunk = mapChunks[2]; + } + else { + if (topX < 0) { + leftTopMapChunk = mapChunks[1]; + rightTopMapChunk = mapChunks[1]; + } + if (leftZ < 0) { + leftTopMapChunk = mapChunks[2]; + leftBotMapChunk = mapChunks[2]; + } + } + + topX = GameMath.Mod(topX, chunkSize); + leftZ = GameMath.Mod(leftZ, chunkSize); + + leftTop = leftTopMapChunk == null ? 0 : Math.Sign(mapY - leftTopMapChunk.RainHeightMap[leftZ * chunkSize + topX]); + rightTop = rightTopMapChunk == null ? 0 : Math.Sign(mapY - rightTopMapChunk.RainHeightMap[rightZ * chunkSize + topX]); + leftBot = leftBotMapChunk == null ? 0 : Math.Sign(mapY - leftBotMapChunk.RainHeightMap[leftZ * chunkSize + botX]); + + float slopeness = (leftTop + rightTop + leftBot); + + if (slopeness > 0) b = 1.2f; + if (slopeness < 0) b = 0.8f; + + b -= 0.15f; //Slope boost value + + if (chunksColumn[localChunkY] == null) { + + continue; + } + + chunksColumn[localChunkY].Unpack( ); + int blockId = chunksColumn[localChunkY].Blocks[MapUtil.Index3d(localpos.X, mapY % chunkSize, localpos.Y, chunkSize, chunkSize)]; + + Block block = ClientAPI.World.Blocks[blockId]; + + tmpPos.Set(chunkSize * chunkPos.X + localpos.X, mapY, chunkSize * chunkPos.Y + localpos.Y); + + int avgCol = block.GetColor(ClientAPI, tmpPos); + int rndCol = block.GetRandomColor(ClientAPI, tmpPos, BlockFacing.UP); + //This is still, an abnormal color - the tint is too blue + int col = ColorUtil.ColorOverlay(avgCol, rndCol, 0.125f); + var packedFormat = ColorUtil.ColorMultiply3Clamped(col, b); + + Color pixelColor = Color.FromArgb(ColorUtil.ColorR(packedFormat), ColorUtil.ColorG(packedFormat), ColorUtil.ColorB(packedFormat)); + + //============ POI Population ================= + if (BlockID_Designators.ContainsKey(blockId)) { + var desig = BlockID_Designators[blockId]; + pixelColor = desig.OverwriteColor; + + if (desig.SpecialAction != null) { + desig.SpecialAction.Invoke(tmpPos, block); + } + } + + chunkImage.SetPixel(localX, localZ, pixelColor); + pixelCount++; + } + + + return chunkImage; + } + #endregion + + + private void GenerateMapHTML( ) + { + string mapFilename = Path.Combine(path, "Automap.html"); + + North_mostChunk = knownChunkTops.Min(tc => tc.Y); + South_mostChunk = knownChunkTops.Max(tc => tc.Y); + East_mostChunk = knownChunkTops.Max(tc => tc.X); + West_mostChunk = knownChunkTops.Min(tc => tc.X); + + + using (StreamWriter outputText = new StreamWriter(File.Open(mapFilename, FileMode.Create, FileAccess.Write))) { + 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( );// + + 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:{West_mostChunk}, E: {East_mostChunk}, N:{North_mostChunk}, S:{South_mostChunk} "); + 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, E"); + tableWriter.RenderEndTag( ); + + for (int xAxisT = West_mostChunk; xAxisT <= East_mostChunk; xAxisT++) { + tableWriter.RenderBeginTag(HtmlTextWriterTag.Th); + tableWriter.Write(xAxisT); + tableWriter.RenderEndTag( ); + } + + tableWriter.RenderBeginTag(HtmlTextWriterTag.Th); + tableWriter.Write("N, W"); + tableWriter.RenderEndTag( ); + + tableWriter.RenderEndTag( ); + tableWriter.RenderEndTag( ); + //###### ################################ + + //###### - Chunk rows & Y-axis cols + tableWriter.RenderBeginTag(HtmlTextWriterTag.Tbody); + //######## for every vertical row + for (int yAxis = North_mostChunk; yAxis <= South_mostChunk; yAxis++) { + tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr); + tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); + tableWriter.Write(yAxis);//legend: Y-axis + tableWriter.RenderEndTag( ); + + for (int xAxis = West_mostChunk; xAxis <= East_mostChunk; xAxis++) { + //###### #### for chunk shard + tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); + if (knownChunkTops.Contains( new Vec2i(xAxis, yAxis))){ + tableWriter.AddAttribute(HtmlTextWriterAttribute.Src, $"{xAxis}_{yAxis}.png"); + tableWriter.AddAttribute(HtmlTextWriterAttribute.Alt, $"X{xAxis}, Y{yAxis}"); + tableWriter.RenderBeginTag(HtmlTextWriterTag.Img); + tableWriter.RenderEndTag( ); + } + else { + tableWriter.Write("?"); + } + tableWriter.RenderEndTag( ); + }//############ ########### + + tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); + tableWriter.Write(yAxis);//legend: Y-axis + tableWriter.RenderEndTag( ); + + tableWriter.RenderEndTag( ); + tableWriter.EndRender( ); + tableWriter.Flush( ); + } + tableWriter.RenderEndTag( ); + + //################ X-Axis ####################### + tableWriter.RenderBeginTag(HtmlTextWriterTag.Tfoot); + tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr); + + tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); + tableWriter.Write("S, E"); + tableWriter.RenderEndTag( ); + + for (int xAxisB = West_mostChunk; xAxisB <= East_mostChunk; xAxisB++) { + tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); + tableWriter.Write(xAxisB); + tableWriter.RenderEndTag( ); + } + + tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); + tableWriter.Write("S, W"); + tableWriter.RenderEndTag( ); + + tableWriter.RenderEndTag( ); + tableWriter.RenderEndTag( ); + //###### ################################ + + + tableWriter.RenderEndTag( );// + tableWriter.EndRender( ); + tableWriter.Flush( ); + } + outputText.Flush( ); + } + + Logger.VerboseDebug("Generated HTML map"); + } + + + + } + +} \ No newline at end of file diff --git a/Automap/Helpers.cs b/Automap/Helpers.cs index fa41c55..f87c0f5 100644 --- a/Automap/Helpers.cs +++ b/Automap/Helpers.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Linq; diff --git a/Automap/assets/automap/config/automap_format.css b/Automap/assets/automap/config/automap_format.css new file mode 100644 index 0000000..c002968 --- /dev/null +++ b/Automap/assets/automap/config/automap_format.css @@ -0,0 +1,44 @@ +table { +border: 1px solid black; +border-collapse: collapse; +overflow:hidden; +} + +thead tr th { +max-width:32px; +margin: 0px; +padding: 0px; +overflow:hidden; +border-right: 1px solid black; +border-bottom: 1px solid black; +font-size:8pt; +font-weight:bold; +font-family: Monospace; +} + +tfoot tr td { +max-width: 32px; +padding: 0px; +overflow:hidden; +border-right: 1px solid black; +border-top: 1px solid black; +font-size:8pt; +font-weight:bold; +font-family: Monospace; +} + +tbody tr { +max-height:32px; +padding: 0px; +overflow:hidden; +} + +tbody td { +padding: 0px; +border: 0px none black; +max-width: 32px; +background-color: white; +overflow:hidden; +width:32px; +font-size: 6pt; +} \ No newline at end of file -- 2.11.0