From 6466988389eaf0f3502714abeb8a8d5031c3a6cf Mon Sep 17 00:00:00 2001 From: melchior Date: Fri, 10 Jan 2020 17:05:46 -0500 Subject: [PATCH] W.I.P. #6: GUI incomplete, injected JSON map state on HTML --- Automap/Automap.csproj | 4 +- Automap/AutomapMod.cs | 21 +- Automap/Data/Designator.cs | 5 +- Automap/Subsystems/AutomapGUIDialog.cs | 78 +++++ .../AutomapSystem.cs} | 323 +++++++++++---------- 5 files changed, 278 insertions(+), 153 deletions(-) create mode 100644 Automap/Subsystems/AutomapGUIDialog.cs rename Automap/{Automap_Internals.cs => Subsystems/AutomapSystem.cs} (90%) diff --git a/Automap/Automap.csproj b/Automap/Automap.csproj index 27176a0..cc558cb 100644 --- a/Automap/Automap.csproj +++ b/Automap/Automap.csproj @@ -76,10 +76,11 @@ - + + @@ -89,6 +90,7 @@ + diff --git a/Automap/AutomapMod.cs b/Automap/AutomapMod.cs index ec224ef..b840d5a 100644 --- a/Automap/AutomapMod.cs +++ b/Automap/AutomapMod.cs @@ -1,17 +1,17 @@ - -using Vintagestory.API.Client; +using Vintagestory.API.Client; using Vintagestory.API.Common; namespace Automap { - public partial class AutomapMod : ModSystem + public class AutomapMod : ModSystem { private ICoreAPI API { get; set; } private ICoreClientAPI ClientAPI { get; set; } private ILogger Logger { get; set; } - + private AutomapSystem _localAutomap; + private AutomapGUIDialog _automapDialog; public override bool ShouldLoad(EnumAppSide forSide) @@ -27,8 +27,13 @@ namespace Automap this.ClientAPI = api as ICoreClientAPI; this.Logger = Mod.Logger; + ClientAPI.Logger.VerboseDebug("Automap Present!"); - ClientAPI.Event.LevelFinalize += StartAutomap; + _localAutomap = new AutomapSystem( this.ClientAPI, this.Logger); + _automapDialog = new AutomapGUIDialog(ClientAPI, _localAutomap); + + ClientAPI.Input.RegisterHotKey(AutomapGUIDialog._automapControlPanelKey, "Automap control panel", GlKeys.A, HotkeyType.GUIOrOtherControls); + ClientAPI.Input.SetHotKeyHandler(AutomapGUIDialog._automapControlPanelKey, ToggleAM_Dialog); } base.StartClientSide(api); @@ -39,7 +44,13 @@ namespace Automap return 0.2; } + private bool ToggleAM_Dialog(KeyCombination comb) + { + if (_automapDialog.IsOpened( )) _automapDialog.TryClose( ); + else _automapDialog.TryOpen( ); + return true; + } } diff --git a/Automap/Data/Designator.cs b/Automap/Data/Designator.cs index c2d36b3..3389f85 100644 --- a/Automap/Data/Designator.cs +++ b/Automap/Data/Designator.cs @@ -20,6 +20,7 @@ namespace Automap public DesignatonAction SpecialAction; public AssetLocation Pattern; public EnumBlockMaterial? Material; + public bool Enabled { get; set; } private Designator( ) { @@ -30,7 +31,8 @@ namespace Automap { this.Pattern = pattern; this.OverwriteColor = overwriteColor; - Material = material; + this.Material = material; + this.Enabled = true; } public Designator(AssetLocation pattern, Color overwriteColor, EnumBlockMaterial? material ,DesignatonAction specialAct ) @@ -39,6 +41,7 @@ namespace Automap this.OverwriteColor = overwriteColor; this.Material = material; this.SpecialAction = specialAct; + this.Enabled = true; } public override string ToString( ) diff --git a/Automap/Subsystems/AutomapGUIDialog.cs b/Automap/Subsystems/AutomapGUIDialog.cs new file mode 100644 index 0000000..6c1dcf3 --- /dev/null +++ b/Automap/Subsystems/AutomapGUIDialog.cs @@ -0,0 +1,78 @@ +using System; + + +using Vintagestory.API.Client; +using Vintagestory.API.Common; + +namespace Automap +{ + public class AutomapGUIDialog : GuiDialog + { + public const string _automapControlPanelKey = "automapControlPanelKey"; + + private ILogger Logger; + private AutomapSystem _automapSystem; + + public override string ToggleKeyCombinationCode { + get + { + return _automapControlPanelKey; + } + } + + public AutomapGUIDialog(ICoreClientAPI capi,AutomapSystem ams) : base(capi) + { + _automapSystem = ams; + Logger = capi.Logger; + SetupDialog( ); + } + + private void SetupDialog( ) + { + ElementBounds dialogBounds = ElementStdBounds.AutosizedMainDialog.WithAlignment(EnumDialogArea.CenterMiddle); + + ElementBounds textBounds = ElementBounds.Fixed(0, 40, 500, 300); + + ElementBounds bgBounds = ElementBounds.Fill.WithFixedPadding(GuiStyle.ElementToDialogPadding); + bgBounds.BothSizing = ElementSizing.FitToChildren; + bgBounds.WithChildren(textBounds); + + ElementBounds toggleBounds = textBounds.CopyOffsetedSibling(3, 5, 5, 2); + toggleBounds.fixedHeight = 18; + toggleBounds.fixedWidth = 50; + + ElementBounds txtStatusBounds = textBounds.CopyOffsetedSibling(0, 20, 2, 4); + txtStatusBounds.fixedHeight = 16; + txtStatusBounds.percentWidth = 1; + + + this.SingleComposer = capi.Gui.CreateCompo("automapControlPanel", dialogBounds) + .AddShadedDialogBG(bgBounds) + .AddDialogTitleBar("Automap Controls", OnTitleBarCloseClicked) + .AddStaticText("Configure Automap settings:", CairoFont.WhiteDetailText( ), textBounds) + .AddToggleButton("Run", CairoFont.ButtonText( ), RunToggle, toggleBounds, "btnRun") + .AddStaticText("Idle.", CairoFont.WhiteSmallText( ).WithFontSize(12), txtStatusBounds, "txtStatus") + .Compose( ); + } + + private void OnTitleBarCloseClicked( ) + { + TryClose( ); + } + + /// + /// Toggle Automap from/to RUN state + /// + /// The toggle. + /// T1. + internal void RunToggle(bool toggle) + { + _automapSystem.Enabled = toggle; + Logger.VerboseDebug("Dialog Changed; [ Automap Enabled: {0} ]", toggle); + var statusText = this.SingleComposer.GetStaticText("txtStatus"); + statusText.SetValue($"Running: {this._automapSystem.Enabled}, Total: {this._automapSystem.updatedChunksTotal}, Nulls: {this._automapSystem.nullChunkCount} " ); + + } + } +} + diff --git a/Automap/Automap_Internals.cs b/Automap/Subsystems/AutomapSystem.cs similarity index 90% rename from Automap/Automap_Internals.cs rename to Automap/Subsystems/AutomapSystem.cs index a7be233..f28a85c 100644 --- a/Automap/Automap_Internals.cs +++ b/Automap/Subsystems/AutomapSystem.cs @@ -13,6 +13,7 @@ using System.Web.UI; using Hjg.Pngcs; using Hjg.Pngcs.Chunks; +using Vintagestory.API.Client; using Vintagestory.API.Common; using Vintagestory.API.MathTools; @@ -20,32 +21,44 @@ using Vintagestory.API.MathTools; namespace Automap { - public partial class AutomapMod + public class AutomapSystem { private Thread cartographer_thread; + private ICoreClientAPI ClientAPI { get; set; } + private ILogger Logger { get; set; } private const string _mapPath = @"Maps"; private const string _chunkPath = @"Chunks"; private const string _domain = @"automap"; - private const string chunkFile_filter = @"*_*.png"; private static Regex chunkShardRegex = new Regex(@"(?[\d]+)_(?[\d]+).png", RegexOptions.Singleline); private ConcurrentDictionary columnCounter = new ConcurrentDictionary(3, 150 ); private ColumnsMetadata chunkTopMetadata; - private PointsOfInterest POIs; - private Dictionary BlockID_Designators; + internal Dictionary BlockID_Designators { get; private set;} + internal bool Enabled { get; set; } + //Run status, Chunks processed, stats, center of map.... + internal uint nullChunkCount; + internal uint updatedChunksTotal; + internal Vec2i startChunkColumn; - private Vec2i startChunkColumn; - private uint lastUpdate; private string path; private IAsset stylesFile; + + public AutomapSystem(ICoreClientAPI clientAPI, ILogger logger) + { + this.ClientAPI = clientAPI; + this.Logger = logger; + ClientAPI.Event.LevelFinalize += EngageAutomap; + } + + #region Internals - private void StartAutomap( ) + 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' @@ -80,7 +93,7 @@ namespace Automap private void AwakenCartographer(float delayed) { - if (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true) { + if (Enabled && (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true)) { #if DEBUG Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState); #endif @@ -92,7 +105,7 @@ namespace Automap //Time to (re)write chunk shards cartographer_thread.Interrupt( ); } - ClientAPI.TriggerChatMessage($"Automap {lastUpdate} changes - MAX (N:{chunkTopMetadata.North_mostChunk},S:{chunkTopMetadata.South_mostChunk},E:{chunkTopMetadata.East_mostChunk}, W:{chunkTopMetadata.West_mostChunk} - TOTAL: {chunkTopMetadata.Count})"); + 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})"); } } @@ -106,7 +119,6 @@ namespace Automap try { uint ejectedItem = 0; uint updatedChunks = 0; - //-- Should dodge enumerator changing underfoot....at a cost. if (!columnCounter.IsEmpty) { @@ -117,7 +129,7 @@ namespace Automap if (mapChunk == null) { Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!", mostActiveCol.Key); - + nullChunkCount++; columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem ); continue; } @@ -147,7 +159,7 @@ namespace Automap if (updatedChunks > 0) { //TODO: ONLY update if chunk bounds have changed! - lastUpdate = updatedChunks; + updatedChunksTotal += updatedChunks; GenerateMapHTML( ); updatedChunks = 0; } @@ -171,7 +183,7 @@ namespace Automap } } - #endregion + private void Prefill_POI_Designators( ) @@ -205,137 +217,6 @@ namespace Automap } - - #region COPYPASTA - //TODO: rewrite - with vertical ray caster, down to bottom-most chunk (for object detection...) - //A partly re-written; ChunkMapLayer :: public int[] GenerateChunkImage(Vec2i chunkPos, IMapChunk mc) - internal void GenerateChunkImage(Vec2i chunkPos, IMapChunk mc, PngWriter pngWriter, 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]; - - 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) - }; - - //pre-create PNG line slices... - ImageLine[ ] lines = Enumerable.Repeat(new object(), chunkSize).Select(l => new ImageLine(pngWriter.ImgInfo)).ToArray(); - - 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); - int col = ColorUtil.ColorOverlay(avgCol, rndCol, 0.125f); - var packedFormat = ColorUtil.ColorMultiply3Clamped(col, b); - - int red = ColorUtil.ColorB(packedFormat); - int green = ColorUtil.ColorG(packedFormat); - int blue = ColorUtil.ColorR(packedFormat); - - - //============ POI Population ================= - if (BlockID_Designators.ContainsKey(blockId)) { - var desig = BlockID_Designators[blockId]; - red = desig.OverwriteColor.R; - green = desig.OverwriteColor.G; - blue = desig.OverwriteColor.B; - - if (desig.SpecialAction != null) { - desig.SpecialAction(ClientAPI, this.POIs, tmpPos, block); - } - } - - ImageLineHelper.SetPixel(lines[localZ], localX, red, green, blue); - - //chunkImage.SetPixel(localX, localZ, pixelColor); - pixelCount++; - } - - for (int row = 0; row < pngWriter.ImgInfo.Rows; row++) { - pngWriter.WriteRow(lines[row], row); - } - - pngWriter.End( ); - } - #endregion - - private void GenerateMapHTML( ) { string mapFilename = Path.Combine(path, "Automap.html"); @@ -359,6 +240,20 @@ namespace Automap 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); @@ -497,9 +392,12 @@ namespace Automap } tableWriter.RenderEndTag( ); - tableWriter.RenderEndTag( ); + + + tableWriter.RenderEndTag( );//### ### + tableWriter.EndRender( ); tableWriter.Flush( ); } @@ -518,7 +416,8 @@ namespace Automap mapChunk.YMax, mostActiveCol.Key.Y * ClientAPI.World.BlockAccessor.ChunkSize); - var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP); + var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP); + data.ChunkAge = TimeSpan.FromHours(ClientAPI.World.Calendar.TotalHours); data.Temperature = climate.Temperature; data.Fertility = climate.Fertility; data.ForestDensity = climate.ForestDensity; @@ -612,6 +511,138 @@ namespace Automap return pngWriter; } + + #endregion + + + #region COPYPASTA + //TODO: rewrite - with vertical ray caster, down to bottom-most chunk (for object detection...) + //A partly re-written; ChunkMapLayer :: public int[] GenerateChunkImage(Vec2i chunkPos, IMapChunk mc) + private void GenerateChunkImage(Vec2i chunkPos, IMapChunk mc, PngWriter pngWriter, 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]; + + 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) + }; + + //pre-create PNG line slices... + ImageLine[ ] lines = Enumerable.Repeat(new object( ), chunkSize).Select(l => new ImageLine(pngWriter.ImgInfo)).ToArray( ); + + 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); + int col = ColorUtil.ColorOverlay(avgCol, rndCol, 0.125f); + var packedFormat = ColorUtil.ColorMultiply3Clamped(col, b); + + int red = ColorUtil.ColorB(packedFormat); + int green = ColorUtil.ColorG(packedFormat); + int blue = ColorUtil.ColorR(packedFormat); + + + //============ POI Population ================= + if (BlockID_Designators.ContainsKey(blockId)) { + var desig = BlockID_Designators[blockId]; + red = desig.OverwriteColor.R; + green = desig.OverwriteColor.G; + blue = desig.OverwriteColor.B; + + if (desig.SpecialAction != null) { + desig.SpecialAction(ClientAPI, this.POIs, tmpPos, block); + } + } + + ImageLineHelper.SetPixel(lines[localZ], localX, red, green, blue); + + //chunkImage.SetPixel(localX, localZ, pixelColor); + pixelCount++; + } + + for (int row = 0; row < pngWriter.ImgInfo.Rows; row++) { + pngWriter.WriteRow(lines[row], row); + } + + pngWriter.End( ); + } + #endregion } } \ No newline at end of file -- 2.11.0