From 7b64e768b8d48fe4809cc20e711fa28cfea21159 Mon Sep 17 00:00:00 2001 From: The Grand Dog Date: Sat, 8 Feb 2020 12:36:55 -0500 Subject: [PATCH] indentation how did you get this far? --- Automap/AutomapMod.cs | 75 +- Automap/Data/BlockDesignator.cs | 80 +- Automap/Data/ColumnMeta.cs | 290 +++---- Automap/Data/EntitiesOfInterest.cs | 75 +- Automap/Data/EntityDesignator.cs | 80 +- Automap/Data/PngMetadataChunk.cs | 88 +-- Automap/Data/PointOfInterest.cs | 56 +- Automap/Designators/DefaultDesignators.cs | 4 - Automap/Helpers.cs | 334 ++++---- Automap/Renderers/AlternateRenderer.cs | 236 +++--- Automap/Renderers/IChunkRenderer.cs | 24 +- Automap/Renderers/StandardRenerer.cs | 287 +++---- Automap/Subsystems/AutomapGUIDialog.cs | 148 ++-- Automap/Subsystems/AutomapSystem.cs | 1225 ++++++++++++++--------------- 14 files changed, 1511 insertions(+), 1491 deletions(-) diff --git a/Automap/AutomapMod.cs b/Automap/AutomapMod.cs index a966a3d..f4d7771 100644 --- a/Automap/AutomapMod.cs +++ b/Automap/AutomapMod.cs @@ -5,52 +5,55 @@ using Vintagestory.API.Common; namespace Automap { - 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 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) - { - return forSide.IsClient(); - } + public override bool ShouldLoad(EnumAppSide forSide) + { + return forSide.IsClient(); + } - public override void StartClientSide(ICoreClientAPI api) - { - this.API = api; + public override void StartClientSide(ICoreClientAPI api) + { + this.API = api; - if (api.Side == EnumAppSide.Client) { - this.ClientAPI = api as ICoreClientAPI; - this.Logger = Mod.Logger; + if (api.Side == EnumAppSide.Client) + { + this.ClientAPI = api as ICoreClientAPI; + this.Logger = Mod.Logger; - ClientAPI.Logger.VerboseDebug("Automap Present!"); - _localAutomap = new AutomapSystem( this.ClientAPI, this.Logger); - _automapDialog = new AutomapGUIDialog(ClientAPI, _localAutomap); + ClientAPI.Logger.VerboseDebug("Automap Present!"); + _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); - } + ClientAPI.Input.RegisterHotKey(AutomapGUIDialog._automapControlPanelKey, "Automap control panel", GlKeys.A, HotkeyType.GUIOrOtherControls); + ClientAPI.Input.SetHotKeyHandler(AutomapGUIDialog._automapControlPanelKey, ToggleAM_Dialog); + } - base.StartClientSide(api); - } + base.StartClientSide(api); + } - public override double ExecuteOrder( ) - { - return 0.2; - } + public override double ExecuteOrder() + { + return 0.2; + } - private bool ToggleAM_Dialog(KeyCombination comb) - { - if (_automapDialog.IsOpened( )) _automapDialog.TryClose( ); - else _automapDialog.TryOpen( ); + private bool ToggleAM_Dialog(KeyCombination comb) + { + if (_automapDialog.IsOpened()) _automapDialog.TryClose(); + else _automapDialog.TryOpen(); - return true; - } - } + return true; + } + + + } } diff --git a/Automap/Data/BlockDesignator.cs b/Automap/Data/BlockDesignator.cs index 353e3c9..ed81041 100644 --- a/Automap/Data/BlockDesignator.cs +++ b/Automap/Data/BlockDesignator.cs @@ -9,45 +9,45 @@ using Vintagestory.API.MathTools; namespace Automap { - public delegate void BlockDesignatonAction(ICoreClientAPI clientAPI, PointsOfInterest poi, BlockPos posn, Block block); - - /// - /// Point of Interest Rule Designator - /// - public class BlockDesignator - { - public Color OverwriteColor; - public BlockDesignatonAction SpecialAction; - public AssetLocation Pattern; - public EnumBlockMaterial? Material; - public bool Enabled { get; set; } - - private BlockDesignator( ) - { - throw new NotSupportedException( ); - } - - public BlockDesignator( AssetLocation pattern , Color overwriteColor, EnumBlockMaterial? material) - { - this.Pattern = pattern; - this.OverwriteColor = overwriteColor; - this.Material = material; - this.Enabled = true; - } - - public BlockDesignator(AssetLocation pattern, Color overwriteColor, EnumBlockMaterial? material ,BlockDesignatonAction specialAct ) - { - this.Pattern = pattern; - this.OverwriteColor = overwriteColor; - this.Material = material; - this.SpecialAction = specialAct; - this.Enabled = true; - } - - public override string ToString( ) - { - return Pattern.ToShortString() +"|"+ OverwriteColor.Name + "|" + Material ?? ""; - } - } + public delegate void BlockDesignatonAction(ICoreClientAPI clientAPI, PointsOfInterest poi, BlockPos posn, Block block); + + /// + /// Point of Interest Rule Designator + /// + public class BlockDesignator + { + public Color OverwriteColor; + public BlockDesignatonAction SpecialAction; + public AssetLocation Pattern; + public EnumBlockMaterial? Material; + public bool Enabled { get; set; } + + private BlockDesignator() + { + throw new NotSupportedException(); + } + + public BlockDesignator(AssetLocation pattern, Color overwriteColor, EnumBlockMaterial? material) + { + this.Pattern = pattern; + this.OverwriteColor = overwriteColor; + this.Material = material; + this.Enabled = true; + } + + public BlockDesignator(AssetLocation pattern, Color overwriteColor, EnumBlockMaterial? material, BlockDesignatonAction specialAct) + { + this.Pattern = pattern; + this.OverwriteColor = overwriteColor; + this.Material = material; + this.SpecialAction = specialAct; + this.Enabled = true; + } + + public override string ToString() + { + return Pattern.ToShortString() + "|" + OverwriteColor.Name + "|" + Material ?? ""; + } + } } diff --git a/Automap/Data/ColumnMeta.cs b/Automap/Data/ColumnMeta.cs index 00d4b09..ff6ed7c 100644 --- a/Automap/Data/ColumnMeta.cs +++ b/Automap/Data/ColumnMeta.cs @@ -11,145 +11,155 @@ using ProtoBuf; namespace Automap { - [ProtoContract] - public struct ColumnMeta - { - [ProtoMember(1)] - public Vec2i Location; - - [ProtoMember(2)] - public TimeSpan ChunkAge;//OLDEST CHUNK. from chunk last edit - - [ProtoMember(3)] - public float Temperature;// Temperature - surface - - [ProtoMember(4)] - public ushort YMax;// Y feature height - - [ProtoMember(5)] - public Dictionary RockRatio;//[Column] Geographic region (rock) Ratio. [BlockID * count] - - [ProtoMember(6)] - public float Fertility; - - [ProtoMember(7)] - public float ForestDensity; - - [ProtoMember(8)] - public float Rainfall; - - [ProtoMember(9)] - public float ShrubDensity; - - [ProtoMember(10)] - public ushort AirBlocks; - - [ProtoMember(11)] - public ushort NonAirBlocks; - - //[ProtoMember(12,OverwriteList = true)] - [ProtoIgnore] - public ushort[ , ] HeightMap; - - public ColumnMeta(Vec2i loc, int chunkSize = 32) - { - Location = loc; - ChunkAge = TimeSpan.Zero; - Temperature = 0f; - YMax = 0; - RockRatio = new Dictionary( 10 ); - Fertility = 0f; - ForestDensity = 0f; - Rainfall = 0f; - ShrubDensity = 0f; - AirBlocks = 0; - NonAirBlocks = 0; - HeightMap = new ushort[chunkSize, chunkSize]; - } - - internal void UpdateFieldsFrom(ClimateCondition climate, IMapChunk mapChunk ,TimeSpan chunkAge) - { - this.ChunkAge = chunkAge; - this.Temperature = climate.Temperature; - this.Fertility = climate.Fertility; - this.ForestDensity = climate.ForestDensity; - this.Rainfall = climate.Rainfall; - this.ShrubDensity = climate.ShrubDensity; - - this.YMax = mapChunk.YMax; - - } - } - - public class ColumnsMetadata : KeyedCollection - { - private ColumnsMetadata( ) - { - throw new NotSupportedException(); - } - - public ColumnsMetadata(Vec2i startChunkColumn) - { - North_mostChunk = startChunkColumn.Y; - South_mostChunk = startChunkColumn.Y; - East_mostChunk = startChunkColumn.X; - West_mostChunk = startChunkColumn.X; - } - - public int North_mostChunk { - get; private set; - } - - public int South_mostChunk { - get; private set; - } - - public int East_mostChunk { - get; private set; - } - - public int West_mostChunk { - get; private set; - } - - protected override Vec2i GetKeyForItem(ColumnMeta item) - { - return item.Location; - } - - internal void Update(ColumnMeta metaData) - { - if (this.Contains(metaData.Location)) { - this.Remove(metaData.Location); - this.Add(metaData); - } - else { - this.Add(metaData); - } - - } - - public new void Add(ColumnMeta newItem) - { - if (North_mostChunk > newItem.Location.Y) { - North_mostChunk = newItem.Location.Y; - } - - if (South_mostChunk < newItem.Location.Y) { - South_mostChunk = newItem.Location.Y; - } - - if (East_mostChunk < newItem.Location.X) { - East_mostChunk = newItem.Location.X; - } - - if (West_mostChunk > newItem.Location.X) { - West_mostChunk = newItem.Location.X; - } - - base.Add(newItem); - } - - } + [ProtoContract] + public struct ColumnMeta + { + [ProtoMember(1)] + public Vec2i Location; + + [ProtoMember(2)] + public TimeSpan ChunkAge;//In game, calendar? + + [ProtoMember(3)] + public float Temperature;// Temperature + + [ProtoMember(4)] + public ushort YMax;// Y feature height + + [ProtoMember(5)] + public Dictionary RockRatio;//(surface) Geographic region (rock) Ratio. [BlockID * count] + + [ProtoMember(6)] + public float Fertility; + + [ProtoMember(7)] + public float ForestDensity; + + [ProtoMember(8)] + public float Rainfall; + + [ProtoMember(9)] + public float ShrubDensity; + + [ProtoMember(10)] + public ushort AirBlocks; + + [ProtoMember(11)] + public ushort NonAirBlocks; + + //[ProtoMember(12,OverwriteList = true)] + [ProtoIgnore] + public ushort[,] HeightMap; + + public ColumnMeta(Vec2i loc, int chunkSize = 32) + { + Location = loc; + ChunkAge = TimeSpan.Zero; + Temperature = 0f; + YMax = 0; + RockRatio = new Dictionary(10); + Fertility = 0f; + ForestDensity = 0f; + Rainfall = 0f; + ShrubDensity = 0f; + AirBlocks = 0; + NonAirBlocks = 0; + HeightMap = new ushort[chunkSize, chunkSize]; + } + + internal void UpdateFieldsFrom(ClimateCondition climate, IMapChunk mapChunk, TimeSpan chunkAge) + { + this.ChunkAge = chunkAge; + this.Temperature = climate.Temperature; + this.Fertility = climate.Fertility; + this.ForestDensity = climate.ForestDensity; + this.Rainfall = climate.Rainfall; + this.ShrubDensity = climate.ShrubDensity; + + this.YMax = mapChunk.YMax; + + } + } + + public class ColumnsMetadata : KeyedCollection + { + private ColumnsMetadata() + { + throw new NotSupportedException(); + } + + public ColumnsMetadata(Vec2i startChunkColumn) + { + North_mostChunk = startChunkColumn.Y; + South_mostChunk = startChunkColumn.Y; + East_mostChunk = startChunkColumn.X; + West_mostChunk = startChunkColumn.X; + } + + public int North_mostChunk + { + get; private set; + } + + public int South_mostChunk + { + get; private set; + } + + public int East_mostChunk + { + get; private set; + } + + public int West_mostChunk + { + get; private set; + } + + protected override Vec2i GetKeyForItem(ColumnMeta item) + { + return item.Location; + } + + internal void Update(ColumnMeta metaData) + { + if (this.Contains(metaData.Location)) + { + this.Remove(metaData.Location); + this.Add(metaData); + } + else + { + this.Add(metaData); + } + + } + + public new void Add(ColumnMeta newItem) + { + if (North_mostChunk > newItem.Location.Y) + { + North_mostChunk = newItem.Location.Y; + } + + if (South_mostChunk < newItem.Location.Y) + { + South_mostChunk = newItem.Location.Y; + } + + if (East_mostChunk < newItem.Location.X) + { + East_mostChunk = newItem.Location.X; + } + + if (West_mostChunk > newItem.Location.X) + { + West_mostChunk = newItem.Location.X; + } + + base.Add(newItem); + } + + } } diff --git a/Automap/Data/EntitiesOfInterest.cs b/Automap/Data/EntitiesOfInterest.cs index f0564fb..49eb1b4 100644 --- a/Automap/Data/EntitiesOfInterest.cs +++ b/Automap/Data/EntitiesOfInterest.cs @@ -8,41 +8,44 @@ using Vintagestory.API.Common.Entities; namespace Automap { - /// - /// Entities of interest. - /// - /// Tracked by ID - these never leave. - public class EntitiesOfInterest - { - private Dictionary entitySet = new Dictionary(50); - - - internal void Upsert(Entity something, string message = @"") - { - if (entitySet.ContainsKey(something.EntityId)) { - var movingPOI = entitySet[something.EntityId]; - movingPOI.Location = something.Pos.AsBlockPos.Copy( ); - movingPOI.Timestamp = DateTimeOffset.UtcNow; - } - else { - PointOfInterest newPOI = new PointOfInterest( ); - newPOI.EntityId = something.EntityId; - newPOI.Location = something.Pos.AsBlockPos.Copy( ); - newPOI.Timestamp = DateTimeOffset.UtcNow; - newPOI.Notes = message; - entitySet.Add(something.EntityId, newPOI ); - } - - } - - - public List PointsList { - get - { - return entitySet.Values.ToList(); - } - } - - } + /// + /// Entities of interest. + /// + /// Tracked by ID - these never leave. + public class EntitiesOfInterest + { + private Dictionary entitySet = new Dictionary(50); + + + internal void Upsert(Entity something, string message = @"") + { + if (entitySet.ContainsKey(something.EntityId)) + { + var movingPOI = entitySet[something.EntityId]; + movingPOI.Location = something.Pos.AsBlockPos.Copy(); + movingPOI.Timestamp = DateTimeOffset.UtcNow; + } + else + { + PointOfInterest newPOI = new PointOfInterest(); + newPOI.EntityId = something.EntityId; + newPOI.Location = something.Pos.AsBlockPos.Copy(); + newPOI.Timestamp = DateTimeOffset.UtcNow; + newPOI.Notes = message; + entitySet.Add(something.EntityId, newPOI); + } + + } + + + public List PointsList + { + get + { + return entitySet.Values.ToList(); + } + } + + } } diff --git a/Automap/Data/EntityDesignator.cs b/Automap/Data/EntityDesignator.cs index 899671d..0b0756a 100644 --- a/Automap/Data/EntityDesignator.cs +++ b/Automap/Data/EntityDesignator.cs @@ -10,45 +10,45 @@ using Vintagestory.API.MathTools; namespace Automap { - public delegate void EntityDesignatonAction(ICoreClientAPI clientAPI, EntitiesOfInterest poi, BlockPos posn, Entity entity); - - /// - /// Point of Interest Rule Designator - /// - public class EntityDesignator - { - public Color OverwriteColor; - public EntityDesignatonAction SpecialAction; - public AssetLocation Pattern; - public EnumEntityState? StateCheck;//Needed? - public bool Enabled { get; set; } - - private EntityDesignator( ) - { - throw new NotSupportedException( ); - } - - public EntityDesignator(AssetLocation pattern, Color overwriteColor, EnumEntityState? state) - { - this.Pattern = pattern; - this.OverwriteColor = overwriteColor; - this.StateCheck = state; - this.Enabled = true; - } - - public EntityDesignator(AssetLocation pattern, Color overwriteColor, EnumEntityState? state, EntityDesignatonAction specialAct) - { - this.Pattern = pattern; - this.OverwriteColor = overwriteColor; - this.StateCheck = state; - this.SpecialAction = specialAct; - this.Enabled = true; - } - - public override string ToString( ) - { - return Pattern.ToShortString( ) + "|" + OverwriteColor.Name + "|" + StateCheck ?? ""; - } - } + public delegate void EntityDesignatonAction(ICoreClientAPI clientAPI, EntitiesOfInterest poi, BlockPos posn, Entity entity); + + /// + /// Point of Interest Rule Designator + /// + public class EntityDesignator + { + public Color OverwriteColor; + public EntityDesignatonAction SpecialAction; + public AssetLocation Pattern; + public EnumEntityState? StateCheck;//Needed? + public bool Enabled { get; set; } + + private EntityDesignator() + { + throw new NotSupportedException(); + } + + public EntityDesignator(AssetLocation pattern, Color overwriteColor, EnumEntityState? state) + { + this.Pattern = pattern; + this.OverwriteColor = overwriteColor; + this.StateCheck = state; + this.Enabled = true; + } + + public EntityDesignator(AssetLocation pattern, Color overwriteColor, EnumEntityState? state, EntityDesignatonAction specialAct) + { + this.Pattern = pattern; + this.OverwriteColor = overwriteColor; + this.StateCheck = state; + this.SpecialAction = specialAct; + this.Enabled = true; + } + + public override string ToString() + { + return Pattern.ToShortString() + "|" + OverwriteColor.Name + "|" + StateCheck ?? ""; + } + } } diff --git a/Automap/Data/PngMetadataChunk.cs b/Automap/Data/PngMetadataChunk.cs index df81944..9e2b45b 100644 --- a/Automap/Data/PngMetadataChunk.cs +++ b/Automap/Data/PngMetadataChunk.cs @@ -7,49 +7,49 @@ using Hjg.Pngcs.Chunks; namespace Automap { - /// - /// Png metadata chunk. - /// - /// There can be only one. (per PNG file) - public class PngMetadataChunk : PngChunkSingle - { - // ID must follow the PNG conventions: four ascii letters, - public readonly static string ID = "cHUK"; - - public ColumnMeta ChunkMetadata { get; set; } - - - public PngMetadataChunk(ImageInfo info) : base(ID, info) - { - - } - - public override ChunkOrderingConstraint GetOrderingConstraint( ) - { - return ChunkOrderingConstraint.NONE; - } - - public override ChunkRaw CreateRawChunk( ) - { - var datas = SerializerUtil.Serialize(ChunkMetadata); - - ChunkRaw rawChunk = createEmptyChunk(datas.Length, true); - rawChunk.Data = datas; - - return rawChunk; - } - - public override void ParseFromRaw(ChunkRaw rawChunk) - { - this.ChunkMetadata = SerializerUtil.Deserialize(rawChunk.Data); - } - - public override void CloneDataFromRead(PngChunk other) - { - PngMetadataChunk clone = ( PngMetadataChunk )other; - this.ChunkMetadata = clone.ChunkMetadata; - } - - } + /// + /// Png metadata chunk. + /// + /// There can be only one. (per PNG file) + public class PngMetadataChunk : PngChunkSingle + { + // ID must follow the PNG conventions: four ascii letters, + public readonly static string ID = "cHUK"; + + public ColumnMeta ChunkMetadata { get; set; } + + + public PngMetadataChunk(ImageInfo info) : base(ID, info) + { + + } + + public override ChunkOrderingConstraint GetOrderingConstraint() + { + return ChunkOrderingConstraint.NONE; + } + + public override ChunkRaw CreateRawChunk() + { + var datas = SerializerUtil.Serialize(ChunkMetadata); + + ChunkRaw rawChunk = createEmptyChunk(datas.Length, true); + rawChunk.Data = datas; + + return rawChunk; + } + + public override void ParseFromRaw(ChunkRaw rawChunk) + { + this.ChunkMetadata = SerializerUtil.Deserialize(rawChunk.Data); + } + + public override void CloneDataFromRead(PngChunk other) + { + PngMetadataChunk clone = (PngMetadataChunk)other; + this.ChunkMetadata = clone.ChunkMetadata; + } + + } } diff --git a/Automap/Data/PointOfInterest.cs b/Automap/Data/PointOfInterest.cs index 116a6f3..438a592 100644 --- a/Automap/Data/PointOfInterest.cs +++ b/Automap/Data/PointOfInterest.cs @@ -6,36 +6,38 @@ using Vintagestory.API.MathTools; namespace Automap { - /// - /// Actual Physical Point in space - that is interesting. - /// - public struct PointOfInterest - { - public string Notes; - public BlockPos Location; - public DateTimeOffset Timestamp; - public long? EntityId; - } + /// + /// Actual Physical Point in space - that is interesting. + /// + public struct PointOfInterest + { + public string Notes; + public BlockPos Location; + public DateTimeOffset Timestamp; + public long? EntityId; + } - public class PointsOfInterest : KeyedCollection - { - protected override BlockPos GetKeyForItem(PointOfInterest item) - { - return item.Location; - } + public class PointsOfInterest : KeyedCollection + { + protected override BlockPos GetKeyForItem(PointOfInterest item) + { + return item.Location; + } - internal void AddReplace(PointOfInterest poi) - { - if (this.Contains(poi.Location)) { - this.Remove(poi.Location); - this.Add(poi); - } - else { - this.Add(poi); - } + internal void AddReplace(PointOfInterest poi) + { + if (this.Contains(poi.Location)) + { + this.Remove(poi.Location); + this.Add(poi); + } + else + { + this.Add(poi); + } - } - } + } + } } diff --git a/Automap/Designators/DefaultDesignators.cs b/Automap/Designators/DefaultDesignators.cs index a6b653f..8c88a89 100644 --- a/Automap/Designators/DefaultDesignators.cs +++ b/Automap/Designators/DefaultDesignators.cs @@ -83,9 +83,7 @@ namespace Automap internal static void DecodeSign(ICoreClientAPI clientAPI, PointsOfInterest poi, BlockPos posn, Block block) { - #if DEBUG clientAPI.Logger.VerboseDebug("Sign Designator Invoked!"); - #endif //sign Text into a POI field... BlockEntitySign signEntity = clientAPI.World.BlockAccessor.GetBlockEntity(posn) as BlockEntitySign; @@ -107,9 +105,7 @@ namespace Automap internal static void DecodePostSign(ICoreClientAPI clientAPI, PointsOfInterest poi, BlockPos posn, Block block) { - #if DEBUG clientAPI.Logger.VerboseDebug("Post-sign Designator Invoked!"); - #endif //sign post Text into a POI field... BlockEntitySignPost signEntity = clientAPI.World.BlockAccessor.GetBlockEntity(posn) as BlockEntitySignPost; diff --git a/Automap/Helpers.cs b/Automap/Helpers.cs index 0c60dba..205f450 100644 --- a/Automap/Helpers.cs +++ b/Automap/Helpers.cs @@ -11,167 +11,177 @@ using Vintagestory.API.MathTools; namespace Automap { - public static class Helpers - { - static Helpers( ) - { - //Called once - thus it can only be in a static constructor. - PngChunk.FactoryRegister(PngMetadataChunk.ID, typeof(PngMetadataChunk)); - } - - /// - /// Hue, Saturation Value colorspace - /// - /// The color equiv. - /// 0 - 360 for hue. - /// 0 - 1 for saturation or value.. - /// 0 - 1 for saturation or value.. - public static Color FromHSV(double hue, double saturation, double value) - { - int hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6; - double f = hue / 60 - Math.Floor(hue / 60); - - value = value * 255; - int v = Convert.ToInt32(value); - int p = Convert.ToInt32(value * (1 - saturation)); - int q = Convert.ToInt32(value * (1 - f * saturation)); - int t = Convert.ToInt32(value * (1 - (1 - f) * saturation)); - - if (hi == 0) - return Color.FromArgb(255, v, t, p); - else if (hi == 1) - return Color.FromArgb(255, q, v, p); - else if (hi == 2) - return Color.FromArgb(255, p, v, t); - else if (hi == 3) - return Color.FromArgb(255, p, q, v); - else if (hi == 4) - return Color.FromArgb(255, t, p, v); - else - return Color.FromArgb(255, v, p, q); - } - - public static string PrettyCoords(this BlockPos location, ICoreClientAPI ClientApi) - { - var start = ClientApi.World.DefaultSpawnPosition.AsBlockPos; - - return string.Format("X{0}, Y{1}, Z{2}", location.X - start.X, location.Y, location.Z - start.Z ); - } - - /// - /// Chunk location to User display coordinate system - /// - /// Friendly string - /// Chunk Coords. - public static string PrettyCoords(this Vec2i location, ICoreClientAPI ClientApi) - { - var start = ClientApi.World.DefaultSpawnPosition.AsBlockPos; - var chunkSize = ClientApi.World.BlockAccessor.ChunkSize; - - return string.Format("X{0}, Z{1}", (location.X*chunkSize) - start.X, (location.Y*chunkSize) - start.Z); - } - - public static BlockPos AverageHighestPos(List positions) - { - int x = 0, y = 0, z = 0, length = positions.Count; - foreach (BlockPos pos in positions) - { - x += pos.X; - y = Math.Max(y, pos.Y);//Mutant Y-axis, take "HIGHEST" - z += pos.Z; - } - return new BlockPos(x/ length, y, z / length); - } - - public static BlockPos PickRepresentativePosition(List positions) - { - var averagePos = AverageHighestPos( positions ); - if ( positions.Any( pos => pos.X == averagePos.X && pos.Y == averagePos.Y && pos.Z == averagePos.Z ) ) { - return averagePos;//lucky ~ center was it! - } - - //Otherwise...pick one - var whichever = positions.Last(poz => poz.Y == averagePos.Y); - - return whichever; - } - - - - /// - /// Find a BLOCK partial path match: BlockID - /// - /// Matching finds - /// Asset name. - public static Dictionary ArbitrarytBlockIdHunter(this ICoreAPI CoreApi ,AssetLocation assetName, EnumBlockMaterial? material = null) - { - Dictionary arbBlockIDTable = new Dictionary( ); - uint emptyCount = 0; - - if (CoreApi.World.Blocks != null) { - - #if DEBUG - CoreApi.World.Logger.VerboseDebug(" World Blocks [Count: {0}]", CoreApi.World.Blocks.Count); - #endif - //If Brute force won't work; use GROOT FORCE! - //var theBlock = ClientApi.World.BlockAccessor.GetBlock(0); - - if (!material.HasValue) { - foreach (Block blk in CoreApi.World.Blocks) { - if (blk.IsMissing || blk.Id == 0 || blk.BlockId == 0) { - emptyCount++; - } else if (blk.Code != null && blk.Code.BeginsWith(assetName.Domain, assetName.Path)) { - #if DEBUG - //CoreApi.World.Logger.VerboseDebug("Block: [{0} ({1})] = #{2}", blk.Code.Path, blk.BlockMaterial, blk.BlockId); - #endif - - arbBlockIDTable.Add(blk.BlockId, blk.Code.Path); - } - } - } else { - foreach (Block blk in CoreApi.World.Blocks) { - if (blk.IsMissing || blk.Id == 0 || blk.BlockId == 0) { - emptyCount++; - } else if (blk.Code != null && material.Value == blk.BlockMaterial && blk.Code.BeginsWith(assetName.Domain, assetName.Path)) { - #if DEBUG - //CoreApi.World.Logger.VerboseDebug("Block: [{0} ({1})] = #{2}", blk.Code.Path, blk.BlockMaterial, blk.BlockId); - #endif - - arbBlockIDTable.Add(blk.BlockId, blk.Code.Path); - } - } - } - - #if DEBUG - CoreApi.World.Logger.VerboseDebug("Block gaps: {0}", emptyCount); - #endif - } - - return arbBlockIDTable; - } - - - - /// - /// Chunk local index. Not block position! - /// - /// Clamps to 5 bit ranges automagically - public static int ChunkBlockIndicie16(int X_index, int Y_index, int Z_index) - { - return ((Y_index & 31) * 32 + (Z_index & 31)) * 32 + (X_index & 31); - } - - /// - /// Chunk index converted from block position (in world) - /// - /// The block indicie. - /// Block position. - /// Clamps to 5 bit ranges automagically - public static int ChunkBlockIndicie16(BlockPos blockPos) - { - //Chunk masked - return ((blockPos.Y & 31) * 32 + (blockPos.Z & 31)) * 32 + (blockPos.X & 31); - } - } + public static class Helpers + { + static Helpers() + { + //Called once - thus it can only be in a static constructor. + PngChunk.FactoryRegister(PngMetadataChunk.ID, typeof(PngMetadataChunk)); + } + + /// + /// Hue, Saturation Value colorspace + /// + /// The color equiv. + /// 0 - 360 for hue. + /// 0 - 1 for saturation or value.. + /// 0 - 1 for saturation or value.. + public static Color FromHSV(double hue, double saturation, double value) + { + int hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6; + double f = hue / 60 - Math.Floor(hue / 60); + + value *= 255; + int v = Convert.ToInt32(value); + int p = Convert.ToInt32(value * (1 - saturation)); + int q = Convert.ToInt32(value * (1 - f * saturation)); + int t = Convert.ToInt32(value * (1 - (1 - f) * saturation)); + + return hi switch + { + 0 => Color.FromArgb(255, v, t, p), + 1 => Color.FromArgb(255, q, v, p), + 2 => Color.FromArgb(255, p, v, t), + 3 => Color.FromArgb(255, p, q, v), + 4 => Color.FromArgb(255, t, p, v), + _ => Color.FromArgb(255, v, p, q), + }; + } + + public static string PrettyCoords(this BlockPos location, ICoreClientAPI ClientApi) + { + var start = ClientApi.World.DefaultSpawnPosition.AsBlockPos; + + return string.Format("X{0}, Y{1}, Z{2}", location.X - start.X, location.Y, location.Z - start.Z); + } + + /// + /// Chunk location to User display coordinate system + /// + /// Friendly string + /// Chunk Coords. + public static string PrettyCoords(this Vec2i location, ICoreClientAPI ClientApi) + { + var start = ClientApi.World.DefaultSpawnPosition.AsBlockPos; + var chunkSize = ClientApi.World.BlockAccessor.ChunkSize; + + return string.Format("X{0}, Z{1}", (location.X * chunkSize) - start.X, (location.Y * chunkSize) - start.Z); + } + + public static BlockPos AverageHighestPos(List positions) + { + int x = 0, y = 0, z = 0, length = positions.Count; + foreach (BlockPos pos in positions) + { + x += pos.X; + y = Math.Max(y, pos.Y);//Mutant Y-axis, take "HIGHEST" + z += pos.Z; + } + return new BlockPos(x / length, y, z / length); + } + + public static BlockPos PickRepresentativePosition(List positions) + { + var averagePos = AverageHighestPos(positions); + if (positions.Any(pos => pos.X == averagePos.X && pos.Y == averagePos.Y && pos.Z == averagePos.Z)) + { + return averagePos;//lucky ~ center was it! + } + + //Otherwise...pick one + var whichever = positions.Last(poz => poz.Y == averagePos.Y); + + return whichever; + } + + + + /// + /// Find a BLOCK partial path match: BlockID + /// + /// Matching finds + /// Asset name. + public static Dictionary ArbitrarytBlockIdHunter(this ICoreAPI CoreApi, AssetLocation assetName, EnumBlockMaterial? material = null) + { + Dictionary arbBlockIDTable = new Dictionary(); + uint emptyCount = 0; + + if (CoreApi.World.Blocks != null) + { + +#if DEBUG + CoreApi.World.Logger.VerboseDebug(" World Blocks [Count: {0}]", CoreApi.World.Blocks.Count); +#endif + //If Brute force won't work; use GROOT FORCE! + //var theBlock = ClientApi.World.BlockAccessor.GetBlock(0); + + if (!material.HasValue) + { + foreach (Block blk in CoreApi.World.Blocks) + { + if (blk.IsMissing || blk.Id == 0 || blk.BlockId == 0) + { + emptyCount++; + } + else if (blk.Code != null && blk.Code.BeginsWith(assetName.Domain, assetName.Path)) + { +#if DEBUG + //CoreApi.World.Logger.VerboseDebug("Block: [{0} ({1})] = #{2}", blk.Code.Path, blk.BlockMaterial, blk.BlockId); +#endif + + arbBlockIDTable.Add(blk.BlockId, blk.Code.Path); + } + } + } + else + { + foreach (Block blk in CoreApi.World.Blocks) + { + if (blk.IsMissing || blk.Id == 0 || blk.BlockId == 0) + { + emptyCount++; + } + else if (blk.Code != null && material.Value == blk.BlockMaterial && blk.Code.BeginsWith(assetName.Domain, assetName.Path)) + { +#if DEBUG + //CoreApi.World.Logger.VerboseDebug("Block: [{0} ({1})] = #{2}", blk.Code.Path, blk.BlockMaterial, blk.BlockId); +#endif + + arbBlockIDTable.Add(blk.BlockId, blk.Code.Path); + } + } + } + +#if DEBUG + CoreApi.World.Logger.VerboseDebug("Block gaps: {0}", emptyCount); +#endif + } + + return arbBlockIDTable; + } + + + + /// + /// Chunk local index. Not block position! + /// + /// Clamps to 5 bit ranges automagically + public static int ChunkBlockIndicie16(int X_index, int Y_index, int Z_index) + { + return ((Y_index & 31) * 32 + (Z_index & 31)) * 32 + (X_index & 31); + } + + /// + /// Chunk index converted from block position (in world) + /// + /// The block indicie. + /// Block position. + /// Clamps to 5 bit ranges automagically + public static int ChunkBlockIndicie16(BlockPos blockPos) + { + //Chunk masked + return ((blockPos.Y & 31) * 32 + (blockPos.Z & 31)) * 32 + (blockPos.X & 31); + } + } } diff --git a/Automap/Renderers/AlternateRenderer.cs b/Automap/Renderers/AlternateRenderer.cs index 93fcd16..010839f 100644 --- a/Automap/Renderers/AlternateRenderer.cs +++ b/Automap/Renderers/AlternateRenderer.cs @@ -9,119 +9,127 @@ using Vintagestory.API.MathTools; namespace Automap { - public class AlternateRenderer : IChunkRenderer - { - private readonly int chunkSize; - - - /// - /// V.G.D:'s Alternative renderer - /// - /// Client API. - /// Logger. - public AlternateRenderer(ICoreClientAPI clientAPI, ILogger logger) : base(clientAPI, logger) - { - chunkSize = ClientAPI.World.BlockAccessor.ChunkSize; - } - - public override void GenerateChunkPngShard(Vec2i chunkPos, IMapChunk mc, ColumnMeta metaData, PngWriter pngWriter, out uint pixelCount) - { - pixelCount = 0; - BlockPos tmpPos = new BlockPos( ); - Vec2i localpos = new Vec2i( ); - 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? - } - - //pre-create PNG line slices... - ImageLine[ ] lines = Enumerable.Repeat(new object( ), chunkSize).Select(l => new ImageLine(pngWriter.ImgInfo)).ToArray( ); - ushort[ ] allMapYs = mc.RainHeightMap; - for (int posIndex = 0; posIndex < (chunkSize * chunkSize); posIndex++) { - int currY = allMapYs[posIndex]; - int localChunkY = currY / chunkSize; - if (localChunkY >= (chunksColumn.Length)) continue; //Out of range! - if (chunksColumn[localChunkY] == null) continue; - - MapUtil.PosInt2d(posIndex, chunkSize, localpos); - int localX = localpos.X; - int localZ = localpos.Y; - - chunksColumn[localChunkY].Unpack( ); - int blockId = chunksColumn[localChunkY].Blocks[MapUtil.Index3d(localX, currY % chunkSize, localZ, chunkSize, chunkSize)]; - - Block block = ClientAPI.World.Blocks[blockId]; - - tmpPos.Set(chunkSize * chunkPos.X + localX, currY, chunkSize * chunkPos.Y + localZ); - - int red; - int green; - int blue; - - //============ POI Population ================= - if (BlockID_Designators.ContainsKey(blockId)) { - var desig = BlockID_Designators[blockId]; - red = desig.OverwriteColor.R; - green = desig.OverwriteColor.G; - blue = desig.OverwriteColor.B; - - - ImageLineHelper.SetPixel(lines[localZ], localX, red, green, blue); - continue; - } - - float b = GetSlope(localX, localZ, allMapYs); - - int col = block.GetColor(ClientAPI, tmpPos); - int packedFormat = ColorUtil.ColorMultiply3Clamped(col, b); - - red = ColorUtil.ColorB(packedFormat); - green = ColorUtil.ColorG(packedFormat); - blue = ColorUtil.ColorR(packedFormat); - 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( ); - } - - private float GetSlope(int x, int y, ushort[ ] heightMap) - { - int baseY = heightMap[MapUtil.Index2d(x, y, chunkSize)]; - float runningY = 0; - // check bounds. i hate this. - int locIndex; - if (x > 0) { - locIndex = MapUtil.Index2d(x - 1, y, chunkSize); - runningY += (baseY - heightMap[locIndex]); - } - if (x < chunkSize - 1) { - locIndex = MapUtil.Index2d(x + 1, y, chunkSize); - runningY += (baseY - heightMap[locIndex]); - } - if (y > 0) { - locIndex = MapUtil.Index2d(x, y - 1, chunkSize); - runningY += (baseY - heightMap[locIndex]); - } - if (y < chunkSize - 1) { - locIndex = MapUtil.Index2d(x, y + 1, chunkSize); - runningY += (baseY - heightMap[locIndex]); - } - runningY /= 4; // average now - runningY /= 5; // idk - return 1 + runningY; - } - } + public class AlternateRenderer : IChunkRenderer + { + private readonly int chunkSize; + + + /// + /// V.G.D:'s Alternative renderer + /// + /// Client API. + /// Logger. + public AlternateRenderer(ICoreClientAPI clientAPI, ILogger logger) : base(clientAPI, logger) + { + chunkSize = ClientAPI.World.BlockAccessor.ChunkSize; + } + + public override void GenerateChunkPngShard(Vec2i chunkPos, IMapChunk mc, ColumnMeta metaData, PngWriter pngWriter, out uint pixelCount) + { + pixelCount = 0; + BlockPos tmpPos = new BlockPos(); + Vec2i localpos = new Vec2i(); + 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? + } + + //pre-create PNG line slices... + ImageLine[] lines = Enumerable.Repeat(new object(), chunkSize).Select(l => new ImageLine(pngWriter.ImgInfo)).ToArray(); + ushort[] allMapYs = mc.RainHeightMap; + for (int posIndex = 0; posIndex < (chunkSize * chunkSize); posIndex++) + { + int currY = allMapYs[posIndex]; + int localChunkY = currY / chunkSize; + if (localChunkY >= (chunksColumn.Length)) continue; //Out of range! + if (chunksColumn[localChunkY] == null) continue; + + MapUtil.PosInt2d(posIndex, chunkSize, localpos); + int localX = localpos.X; + int localZ = localpos.Y; + + chunksColumn[localChunkY].Unpack(); + int blockId = chunksColumn[localChunkY].Blocks[MapUtil.Index3d(localX, currY % chunkSize, localZ, chunkSize, chunkSize)]; + + Block block = ClientAPI.World.Blocks[blockId]; + + tmpPos.Set(chunkSize * chunkPos.X + localX, currY, chunkSize * chunkPos.Y + localZ); + + int red; + int green; + int blue; + + //============ POI Population ================= + if (BlockID_Designators.ContainsKey(blockId)) + { + var desig = BlockID_Designators[blockId]; + red = desig.OverwriteColor.R; + green = desig.OverwriteColor.G; + blue = desig.OverwriteColor.B; + + + ImageLineHelper.SetPixel(lines[localZ], localX, red, green, blue); + continue; + } + + float b = GetSlope(localX, localZ, allMapYs); + + int col = block.GetColor(ClientAPI, tmpPos); + int packedFormat = ColorUtil.ColorMultiply3Clamped(col, b); + + red = ColorUtil.ColorB(packedFormat); + green = ColorUtil.ColorG(packedFormat); + blue = ColorUtil.ColorR(packedFormat); + 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(); + } + + private float GetSlope(int x, int y, ushort[] heightMap) + { + int baseY = heightMap[MapUtil.Index2d(x, y, chunkSize)]; + float runningY = 0; + // check bounds. i hate this. + int locIndex; + if (x > 0) + { + locIndex = MapUtil.Index2d(x - 1, y, chunkSize); + runningY += (baseY - heightMap[locIndex]); + } + if (x < chunkSize - 1) + { + locIndex = MapUtil.Index2d(x + 1, y, chunkSize); + runningY += (baseY - heightMap[locIndex]); + } + if (y > 0) + { + locIndex = MapUtil.Index2d(x, y - 1, chunkSize); + runningY += (baseY - heightMap[locIndex]); + } + if (y < chunkSize - 1) + { + locIndex = MapUtil.Index2d(x, y + 1, chunkSize); + runningY += (baseY - heightMap[locIndex]); + } + runningY /= 4; // average now + runningY /= 5; // idk + return 1 + runningY; + } + } } diff --git a/Automap/Renderers/IChunkRenderer.cs b/Automap/Renderers/IChunkRenderer.cs index f233499..a7930a1 100644 --- a/Automap/Renderers/IChunkRenderer.cs +++ b/Automap/Renderers/IChunkRenderer.cs @@ -9,19 +9,19 @@ using Vintagestory.API.MathTools; namespace Automap { - public abstract class IChunkRenderer - { - public virtual ICoreClientAPI ClientAPI { get; protected set; } - public virtual ILogger Logger { get; protected set; } - public virtual Dictionary BlockID_Designators { get; set; } + public abstract class IChunkRenderer + { + public virtual ICoreClientAPI ClientAPI { get; protected set; } + public virtual ILogger Logger { get; protected set; } + public virtual Dictionary BlockID_Designators { get; set; } - protected IChunkRenderer(ICoreClientAPI clientAPI, ILogger logger) - { - this.ClientAPI = clientAPI; - this.Logger = logger; - } + protected IChunkRenderer(ICoreClientAPI clientAPI, ILogger logger) + { + this.ClientAPI = clientAPI; + this.Logger = logger; + } - public abstract void GenerateChunkPngShard(Vec2i chunkPos, IMapChunk mapChunk, ColumnMeta metaData, PngWriter pngWriter, out uint pixelCount); - } + public abstract void GenerateChunkPngShard(Vec2i chunkPos, IMapChunk mapChunk, ColumnMeta metaData, PngWriter pngWriter, out uint pixelCount); + } } diff --git a/Automap/Renderers/StandardRenerer.cs b/Automap/Renderers/StandardRenerer.cs index 947b25a..71ea0b9 100644 --- a/Automap/Renderers/StandardRenerer.cs +++ b/Automap/Renderers/StandardRenerer.cs @@ -9,144 +9,153 @@ using Vintagestory.API.MathTools; namespace Automap { - public class StandardRenderer : IChunkRenderer - { - private readonly int chunkSize; - - - /// - /// Renders shards similar to the Default VS version, plus P.O.I. markings. - /// - /// Client API. - /// Logger. - public StandardRenderer(ICoreClientAPI clientAPI, ILogger logger) : base (clientAPI, logger) - { - chunkSize = ClientAPI.World.BlockAccessor.ChunkSize; - } - - public override void GenerateChunkPngShard(Vec2i chunkPos, IMapChunk mc, ColumnMeta metaData, PngWriter pngWriter, out uint pixelCount) - { - pixelCount = 0; - BlockPos tmpPos = new BlockPos( ); - Vec2i localpos = new Vec2i( ); - - 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! - - 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]; - - if (desig.Enabled) - { - red = desig.OverwriteColor.R; - green = desig.OverwriteColor.G; - blue = desig.OverwriteColor.B; - } - } - - 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( ); - } - } + public class StandardRenderer : IChunkRenderer + { + private readonly int chunkSize; + + + /// + /// Renders shards similar to the Default VS version, plus P.O.I. markings. + /// + /// Client API. + /// Logger. + public StandardRenderer(ICoreClientAPI clientAPI, ILogger logger) : base(clientAPI, logger) + { + chunkSize = ClientAPI.World.BlockAccessor.ChunkSize; + } + + public override void GenerateChunkPngShard(Vec2i chunkPos, IMapChunk mc, ColumnMeta metaData, PngWriter pngWriter, out uint pixelCount) + { + pixelCount = 0; + BlockPos tmpPos = new BlockPos(); + Vec2i localpos = new Vec2i(); + + 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! + + 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]; + + if (desig.Enabled) + { + red = desig.OverwriteColor.R; + green = desig.OverwriteColor.G; + blue = desig.OverwriteColor.B; + } + } + + 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(); + } + } } diff --git a/Automap/Subsystems/AutomapGUIDialog.cs b/Automap/Subsystems/AutomapGUIDialog.cs index 3958d03..c46e6aa 100644 --- a/Automap/Subsystems/AutomapGUIDialog.cs +++ b/Automap/Subsystems/AutomapGUIDialog.cs @@ -6,84 +6,74 @@ 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( ); - } - - //Event for GUI status display - - 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, 26, 5, 2); - toggleBounds.fixedHeight = 24; - toggleBounds.fixedWidth = 64; - - 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( ); - - //Controls for ALL Block & Entity Designators (Enable/Disable) - //_automapSystem.BlockID_Designators - //_automapSystem.Entity_Designators - //Renderer selection - //Message verbosity? Speed? - - //A Button to add POI - notes manually (when AM running) - - } - - 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} " ); - - } - } + 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/Subsystems/AutomapSystem.cs b/Automap/Subsystems/AutomapSystem.cs index faea6b5..5b95308 100644 --- a/Automap/Subsystems/AutomapSystem.cs +++ b/Automap/Subsystems/AutomapSystem.cs @@ -16,628 +16,617 @@ using Hjg.Pngcs.Chunks; using Vintagestory.API.Client; using Vintagestory.API.Common; using Vintagestory.API.Common.Entities; -using Vintagestory.API.Config; using Vintagestory.API.MathTools; using Vintagestory.Common; namespace Automap { - public class AutomapSystem - { - private Thread cartographer_thread; - private ICoreClientAPI ClientAPI { get; set; } - private ILogger Logger { get; set; } - private IChunkRenderer ChunkRenderer { 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 EntitiesOfInterest EOIs; - - internal Dictionary BlockID_Designators { get; private set;} - internal Dictionary Entity_Designators { get; private set; } - internal Dictionary RockIdCodes { get; private set; } - - internal bool Enabled { get; set; } - //Run status, Chunks processed, stats, center of map.... - internal uint nullChunkCount, updatedChunksTotal; - internal Vec2i startChunkColumn; - - private readonly int chunkSize; - private string path; - private IAsset stylesFile; - - - public AutomapSystem(ICoreClientAPI clientAPI, ILogger logger) - { - this.ClientAPI = clientAPI; - this.Logger = logger; - chunkSize = ClientAPI.World.BlockAccessor.ChunkSize; - ClientAPI.Event.LevelFinalize += EngageAutomap; - - //TODO:Choose which one from GUI - this.ChunkRenderer = new StandardRenderer(clientAPI, logger); - } - - - #region Internals - 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' - - 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 / chunkSize), (ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Z / chunkSize)); - chunkTopMetadata = new ColumnsMetadata(startChunkColumn); - - Logger.Notification("AUTOMAP Start {0}", startChunkColumn); - Reload_Metadata( ); - - 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 (Enabled && (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 - } - - } - - - 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); - - 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); - } - - } - } - - if (updatedChunks > 0) { - //What about chunk updates themselves; a update bitmap isn't kept... - updatedChunksTotal += 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); - } - } - - private void Prefill_POI_Designators( ) - { - 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); - - //Add special marker types for BlockID's of "Interest", overwrite colour, and method - - Install_POI_Designators(DefaultDesignators.DefaultBlockDesignators(), DefaultDesignators.DefaultEntityDesignators()); - } - - private void Install_POI_Designators(ICollection blockDesig, List entDesig) - { - 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); - } - - - } - - //TODO: Convert to RAZOR model - private void GenerateMapHTML( ) - { - 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( ); - - //################ X-Axis ####################### - tableWriter.RenderBeginTag(HtmlTextWriterTag.Tfoot); - tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr); - - tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); - tableWriter.Write("S, W"); - tableWriter.RenderEndTag( ); - - 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( ); - //###### ################################ - - - 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( ); - - - - - tableWriter.RenderEndTag( );//### ### - - tableWriter.EndRender( ); - tableWriter.Flush( ); - } - outputText.Flush( ); - } - - Logger.VerboseDebug("Generated HTML map"); - } - - - - 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); - - var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP); - data.UpdateFieldsFrom(climate, mapChunk,TimeSpan.FromHours(ClientAPI.World.Calendar.TotalHours)); - - return data; - } - - /// - /// Reload chunk bounds from chunk shards - /// - /// The metadata. - private void Reload_Metadata( ) - { - var worldmapDir = new DirectoryInfo(path); - - if (worldmapDir.Exists) { - - var files = worldmapDir.GetFiles(chunkFile_filter); - - if (files.Length > 0) { - #if DEBUG - Logger.VerboseDebug("{0} Existing world chunk shards", files.Length); - #endif - - - - foreach (var shardFile in files) { - - 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( )) - { - PngReader pngRead = new PngReader(fileStream ); - pngRead.ReadSkippingAllRows( ); - pngRead.End( ); - - PngMetadataChunk metadataFromPng = pngRead.GetChunksList( ).GetById1(PngMetadataChunk.ID) as PngMetadataChunk; - - chunkTopMetadata.Add(metadataFromPng.ChunkMetadata); - } - - } - } - - } - } - 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... - /// - /// Chunk Coordinate - /// Map chunk. - /// Chunk metadata - private void ProcessChunkBlocks(Vec2i key, IMapChunk mapChunk, ColumnMeta chunkMeta) - { - - 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); - #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 - - foreach (var blockEnt in chunkData.BlockEntities) { - - if (blockEnt != null && blockEnt.Block != null && BlockID_Designators.ContainsKey(blockEnt.Block.BlockId)) - { - 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; - } - - if (RockIdCodes.ContainsKey(aBlockId)) { - if (chunkMeta.RockRatio.ContainsKey(aBlockId)) { chunkMeta.RockRatio[aBlockId]++; } else { chunkMeta.RockRatio.Add(aBlockId, 1); } - } - - chunkMeta.NonAirBlocks++; - - //Heightmap - if (chunkMeta.HeightMap[X_index, Z_index] == 0) - { chunkMeta.HeightMap[X_index, Z_index] = ( ushort )(Y_index + (targetChunkY * chunkSize)); } - - } - while (X_index++ < (chunkSize - 1)); - X_index = 0; - } - while (Z_index++ < (chunkSize - 1)); - Z_index = 0; - } - while (Y_index++ < (chunkSize - 1)); - - } - } - - 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 - - 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); - } - - } - - - } - - - - - - #endregion - - - } + public class AutomapSystem + { + private Thread cartographer_thread; + private ICoreClientAPI ClientAPI { get; set; } + private ILogger Logger { get; set; } + private IChunkRenderer ChunkRenderer { 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 EntitiesOfInterest EOIs; + + internal Dictionary BlockID_Designators { get; private set; } + internal Dictionary Entity_Designators { get; private set; } + + internal bool Enabled { get; set; } + //Run status, Chunks processed, stats, center of map.... + internal uint nullChunkCount, updatedChunksTotal; + internal Vec2i startChunkColumn; + + private readonly int chunkSize; + private string path; + private IAsset stylesFile; + + + public AutomapSystem(ICoreClientAPI clientAPI, ILogger logger) + { + this.ClientAPI = clientAPI; + this.Logger = logger; + chunkSize = ClientAPI.World.BlockAccessor.ChunkSize; + ClientAPI.Event.LevelFinalize += EngageAutomap; + + //TODO:Choose which one from GUI + this.ChunkRenderer = new StandardRenderer(clientAPI, logger); + } + + + #region Internals + 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' + + 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 / chunkSize), (ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Z / chunkSize)); + chunkTopMetadata = new ColumnsMetadata(startChunkColumn); + + Logger.Notification("AUTOMAP Start {0}", startChunkColumn); + Reload_Metadata(); + + ClientAPI.Event.ChunkDirty += ChunkAChanging; + + cartographer_thread = new Thread(Cartographer) + { + Name = "Cartographer", + Priority = ThreadPriority.Lowest, + 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 (Enabled && (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 + } + + } + + + 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); + + + ChunkRenderer.GenerateChunkPngShard(mostActiveCol.Key, mapChunk, chunkMeta, pngWriter, out uint 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); + } + + } + } + + if (updatedChunks > 0) + { + //What about chunk updates themselves; a update bitmap isn't kept... + updatedChunksTotal += 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); + } + } + + private void Prefill_POI_Designators() + { + this.POIs = new PointsOfInterest(); + this.EOIs = new EntitiesOfInterest(); + this.BlockID_Designators = new Dictionary(); + this.Entity_Designators = new Dictionary(); + + //Add special marker types for BlockID's of "Interest", overwrite colour, and method + + Install_POI_Designators(DefaultDesignators.DefaultBlockDesignators(), DefaultDesignators.DefaultEntityDesignators()); + } + + private void Install_POI_Designators(ICollection blockDesig, List entDesig) + { + 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); + } + + + } + + //TODO: Convert to RAZOR model + private void GenerateMapHTML() + { + 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(); + + //################ X-Axis ####################### + tableWriter.RenderBeginTag(HtmlTextWriterTag.Tfoot); + tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr); + + tableWriter.RenderBeginTag(HtmlTextWriterTag.Td); + tableWriter.Write("S, W"); + tableWriter.RenderEndTag(); + + 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(); + //###### ################################ + + + 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(); + + + + + tableWriter.RenderEndTag();//### ### + + tableWriter.EndRender(); + tableWriter.Flush(); + } + outputText.Flush(); + } + + Logger.VerboseDebug("Generated HTML map"); + } + + + + 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); + + var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP); + data.UpdateFieldsFrom(climate, mapChunk, TimeSpan.FromHours(ClientAPI.World.Calendar.TotalHours)); + + return data; + } + + /// + /// Reload chunk bounds from chunk shards + /// + /// The metadata. + private void Reload_Metadata() + { + var worldmapDir = new DirectoryInfo(path); + + if (worldmapDir.Exists) + { + + var files = worldmapDir.GetFiles(chunkFile_filter); + + if (files.Length > 0) + { +#if DEBUG + Logger.VerboseDebug("{0} Existing world chunk shards", files.Length); +#endif + foreach (var shardFile in files) + { + + if (shardFile.Length < 1000) 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(); + PngReader pngRead = new PngReader(fileStream); + pngRead.ReadSkippingAllRows(); + pngRead.End(); + + PngMetadataChunk metadataFromPng = pngRead.GetChunksList().GetById1(PngMetadataChunk.ID) as PngMetadataChunk; + + chunkTopMetadata.Add(metadataFromPng.ChunkMetadata); + + } + } + + } + } + 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) + { + 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... + /// + /// Chunk Coordinate + /// Map chunk. + /// Chunk metadata + private void ProcessChunkBlocks(Vec2i key, IMapChunk mapChunk, ColumnMeta chunkMeta) + { + //TODO: build stack of chunk(s) - surface down to bedrock + int topChunkY = mapChunk.YMax / chunkSize; + WorldChunk chunkData = (Vintagestory.Common.WorldChunk)ClientAPI.World.BlockAccessor.GetChunk(key.X, topChunkY, key.Y); + + if (chunkData.BlockEntities != null && chunkData.BlockEntities.Length > 0) + { +#if DEBUG + Logger.VerboseDebug("Surface@ {0} = BlockEntities: {1}", key, chunkData.BlockEntities.Length); + + foreach (var blockEnt in chunkData.BlockEntities) + { + if (BlockID_Designators.ContainsKey(blockEnt.Block.BlockId)) + { + var designator = BlockID_Designators[blockEnt.Block.BlockId]; + designator.SpecialAction(ClientAPI, POIs, blockEnt.Pos.Copy(), blockEnt.Block); + } + } +#endif + } + + } + + 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 + + var dMatch = Entity_Designators.SingleOrDefault(se => se.Key.Equals(loadedEntity.Value.Code)); + if (dMatch.Value != null) + { + if (dMatch.Value.Enabled) + { + dMatch.Value.SpecialAction(ClientAPI, this.EOIs, loadedEntity.Value.LocalPos.AsBlockPos.Copy(), loadedEntity.Value); + } + } + + } + + + } + + + + + + #endregion + + + } } \ No newline at end of file -- 2.11.0