using System.Threading;
using Hjg.Pngcs;
-
+using Mono.Collections.Generic;
using ProtoBuf;
using Vintagestory.API.Client;
using Vintagestory.API.Common;
+using Vintagestory.API.Common.Entities;
using Vintagestory.API.Config;
using Vintagestory.API.Datastructures;
using Vintagestory.API.MathTools;
public class AutomapSystem
{
private Thread cartographer_thread;
- private Thread snapshotThread;
+
private Snapshotter snapshot;
private ICoreClientAPI ClientAPI { get; set; }
private ILogger Logger { get; set; }
internal const string _mapPath = @"Maps";
internal const string _chunkPath = @"Chunks";
- internal const uint editThreshold = 1;
+ internal const uint editThreshold = 9;
private const string _domain = @"automap";
private const string chunkFile_filter = @"*_*.png";
private const string poiFileName = @"poi_binary";
private const string eoiFileName = @"eoi_binary";
private const string pointsTsvFileName = @"points_of_interest.tsv";
+ private const string plainMetadataFileName = @"map_metadata.txt";
private static Regex chunkShardRegex = new Regex(@"(?<X>[\d]+)_(?<Z>[\d]+)\.png", RegexOptions.Singleline);
private ConcurrentDictionary<Vec2i, ColumnCounter> columnCounters = new ConcurrentDictionary<Vec2i, ColumnCounter>(3, 150);
private ColumnsMetadata chunkTopMetadata;
- private PointsOfInterest POIs = new PointsOfInterest();
- private EntitiesOfInterest EOIs = new EntitiesOfInterest();
+ internal PointsOfInterest POIs = new PointsOfInterest();
+ internal EntitiesOfInterest EOIs = new EntitiesOfInterest();
internal Dictionary<int, BlockDesignator> BlockID_Designators { get; private set; }
internal Dictionary<AssetLocation, EntityDesignator> Entity_Designators { get; private set; }
this.ClientAPI = clientAPI;
this.Logger = logger;
chunkSize = ClientAPI.World.BlockAccessor.ChunkSize;
- ClientAPI.Event.LevelFinalize += EngageAutomap;
+
configuration = config;
+ ClientAPI.Event.LevelFinalize += EngageAutomap;
- //TODO:Choose which one from GUI
- this.ChunkRenderer = new StandardRenderer(clientAPI, logger, this.configuration.SeasonalColors);
+ this.ChunkRenderer = InstantiateChosenRenderer(config.RendererName);
//Listen on bus for commands
ClientAPI.Event.RegisterEventBusListener(CommandListener, 1.0, AutomapSystem.AutomapCommandEventKey);
if (configuration.Autostart)
{
CurrentState = CommandType.Run;
- Logger.Debug("Autostart is Enabled.");
+ Logger.Notification("Autostart is Enabled.");
}
}
IsBackground = true
};
- snapshot = new Snapshotter(path, chunkTopMetadata, chunkSize,ClientAPI.World.Seed );
- snapshotThread = new Thread(Snap)
- {
- Name = "Snapshot",
- Priority = ThreadPriority.Lowest,
- IsBackground = true
- };
-
- ClientAPI.Event.RegisterGameTickListener(AwakenCartographer, 6000);
+ ClientAPI.Event.RegisterGameTickListener(ThreadDecider, 6000);
}
private void ChunkAChanging(Vec3i chunkCoord, IWorldChunk chunk, EnumChunkDirtyReason reason)
}
- private void AwakenCartographer(float delayed)
+ /// <summary>
+ /// Cartographer Thread 'decider'
+ /// </summary>
+ /// <param name="delayed">called delay offset</param>
+ private void ThreadDecider(float delayed)
{
if (CurrentState == CommandType.Run && (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true))
{
-#if DEBUG
- Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState);
-#endif
+ #if DEBUG
+ Logger.VerboseDebug("ThreadDecider re-trigger from [{0}]", cartographer_thread.ThreadState);
+ #endif
if (cartographer_thread.ThreadState.HasFlag(ThreadState.Unstarted))
{
}
else if (CurrentState == CommandType.Snapshot)
{
- if (snapshotThread.ThreadState.HasFlag(ThreadState.Unstarted))
- {
- snapshotThread.Start();
- } else if (snapshotThread.ThreadState.HasFlag(ThreadState.WaitSleepJoin))
- {
- snapshotThread.Interrupt();
+ //Prepare for taking a snopshot
+ if (snapshot == null) {
+ snapshot = new Snapshotter(path, chunkTopMetadata, chunkSize, ClientAPI.World.Seed);
+ #if DEBUG
+ Logger.VerboseDebug("Starting new Snapshot: {0} Wx{1} Hx{2}", snapshot.fileName, snapshot.Width, snapshot.Height);
+ #endif
+ snapshot.Take( );
+ }
+ else if (snapshot != null && snapshot.Finished) {
+ #if DEBUG
+ Logger.VerboseDebug("COMPLETED Snapshot: {0} Wx{1} Hx{2}, taking {3}", snapshot.fileName, snapshot.Width, snapshot.Height, snapshot.Timer.Elapsed);
+ #endif
+ snapshot = null;
+ CurrentState = CommandType.Run;
}
}
-
}
if (mapChunk == null)
{
//TODO: REVISIT THIS CHUNK!
+ #if DEBUG
Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!", mostActiveCol.Key);
+ #endif
nullMapCount++;
columnCounters.TryRemove(mostActiveCol.Key, out ejectedItem);
continue;
#endif
}
ProcessChunkBlocks(mostActiveCol.Key, mapChunk, ref chunkMeta);
+ mostActiveCol.Value.SetCutoff(chunkMeta.YMax / chunkSize);
ChunkRenderer.SetupPngImage(mostActiveCol.Key, path, _chunkPath, ref chunkMeta);
ChunkRenderer.GenerateChunkPngShard(mostActiveCol.Key, mapChunk, chunkMeta, ref chunkTopMetadata, out updatedPixels);
Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name);
#endif
PersistPointsData();
+ Write_PlainMetadata( );
}
}
var airBlocksQuery = from airyBlock in ClientAPI.World.Blocks
where airyBlock.MatterState == EnumMatterState.Solid
- where airyBlock.BlockMaterial == EnumBlockMaterial.Plant || airyBlock.BlockMaterial == EnumBlockMaterial.Leaves
- where airyBlock.CollisionBoxes == null || airyBlock.CollisionBoxes.Length == 0
- select airyBlock;
- //^^ 'Solid' phase - 'Plant' Blocks without any boundg box ? Except water...
- this.AiryIdCodes = airBlocksQuery.ToDictionary(aBlk => aBlk.BlockId, aBlk => aBlk.Code.Path);
-
- //Add special marker types for BlockID's of "Interest", overwrite colour, and method
- Reload_POI_Designators();
+ where airyBlock.BlockMaterial == EnumBlockMaterial.Plant || airyBlock.BlockMaterial == EnumBlockMaterial.Leaves
+ where airyBlock.CollisionBoxes == null || airyBlock.CollisionBoxes.Length == 0 || airyBlock.RainPermeable == true
+ select airyBlock;
+ //^^ 'Solid' phase - 'Plant' Blocks without any bounding-box; OR 'Invisible' shapes...
+ var invisibleBlocksQuery = from novisBlock in ClientAPI.World.Blocks
+ where novisBlock.Shape == null || novisBlock.Shape.Base.EndsWith(GlobalConstants.DefaultDomain, @"invisible") //Whaat! [ base: "block/basic/invisible" ]
+ select novisBlock;
+ this.AiryIdCodes = airBlocksQuery.Union(invisibleBlocksQuery).ToDictionary(aBlk => aBlk.BlockId, aBlk => aBlk.Code.Path);
+
+ #if DEBUG
+ foreach (var fluffBlock in AiryIdCodes) {
+ Logger.VerboseDebug("ID#\t{0}:\t{1} IGNORED", fluffBlock.Key, fluffBlock.Value);
+ }
+ Logger.VerboseDebug("Ignoring {0} blocks", AiryIdCodes.Count);
+ #endif
+
+ //Add special marker types for BlockID's of "Interest", overwrite colour, and method
+ Reload_POI_Designators();
}
private void Reload_POI_Designators()
{
- Logger.VerboseDebug("Connecting {0} Configured Block-Designators", configuration.BlockDesignators.Count);
+ uint poisSetup =0, eoiSetup = 0;
foreach (var designator in configuration.BlockDesignators)
{
+ if (designator.Enabled == false) continue;
var blockIDs = Helpers.ArbitrarytBlockIdHunter(ClientAPI, designator.Pattern, designator.Material);
if (blockIDs.Count > 0) { Logger.VerboseDebug("Designator {0} has {1} associated blockIDs", designator.ToString(), blockIDs.Count); }
foreach (var entry in blockIDs)
{
BlockID_Designators.Add(entry.Key, designator);
+ poisSetup++;
}
}
this.ChunkRenderer.BlockID_Designators = BlockID_Designators;
+ Logger.VerboseDebug("Connected {0} IDs from {1} Block-Designators", poisSetup, configuration.BlockDesignators.Count );
- Logger.VerboseDebug("Connecting {0} Configured Entity-Designators", configuration.EntityDesignators.Count);
foreach (var designator in configuration.EntityDesignators)
{
+ if (designator.Enabled == false) continue;
//Get Variants first, from EntityTypes...better be populated!
var matched = ClientAPI.World.EntityTypes.FindAll(entp => entp.Code.BeginsWith(designator.Pattern.Domain, designator.Pattern.Path));
foreach (var match in matched)
- {
+ {
Logger.VerboseDebug("Linked Entity: {0} Designator: {1}", match.Code, designator);
this.Entity_Designators.Add(match.Code, designator);
+ eoiSetup++;
}
-
- //EntityProperties props = ClientAPI.World.GetEntityType(designator.Pattern);
}
-
+ Logger.VerboseDebug("Connected {0} IDs from {1} Entity-Designators", eoiSetup, configuration.EntityDesignators.Count);
}
if (this.POIs.Count > 0)
{
- using (var poiFile = File.OpenWrite(poiPath))
+ using (var poiFile = File.Open(poiPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
Serializer.Serialize<PointsOfInterest>(poiFile, this.POIs);
+ poiFile.Flush(true);
}
}
if (this.EOIs.Count > 0)
{
- using (var eoiFile = File.OpenWrite(eoiPath))
+ using (var eoiFile = File.Open(eoiPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
Serializer.Serialize<EntitiesOfInterest>(eoiFile, this.EOIs);
+ eoiFile.Flush(true);
}
}
using (var tsvWriter = new StreamWriter(pointsTsvPath, false, Encoding.UTF8))
{
- tsvWriter.WriteLine("Name\tDescription\tLocation\tTime\tDestination");
+ tsvWriter.WriteLine("Name\tDescription\tLocation\tTime\tDestination\tEntity_UID");
foreach (var point in this.POIs)
{
tsvWriter.Write(point.Name + "\t");
var notes = point.Notes
- .Replace("\n", "\\n")
+ .Replace('\n', '\x001f')
.Replace("\t", "\\t")
.Replace("\\", "\\\\");
tsvWriter.Write(notes + "\t");
tsvWriter.Write(point.Location.PrettyCoords(ClientAPI) + "\t");
tsvWriter.Write(point.Timestamp.ToString("u") + "\t");
tsvWriter.Write((point.Destination != null ? point.Destination.PrettyCoords(ClientAPI) : "---") +"\t");
+ tsvWriter.Write("null\t");
tsvWriter.WriteLine();
}
foreach (var entity in this.EOIs)
{
tsvWriter.Write(entity.Name + "\t");
var notes = entity.Notes
- .Replace("\n", "\\n")
+ .Replace('\n', '\x001f')
.Replace("\t", "\\t")
.Replace("\\", "\\\\");
tsvWriter.Write(notes + "\t");
tsvWriter.Write(entity.Location.PrettyCoords(ClientAPI) + "\t");
tsvWriter.Write(entity.Timestamp.ToString("u") + "\t");
- tsvWriter.Write("n/a\t");
+ tsvWriter.Write("---\t");
+ tsvWriter.Write(entity.EntityId.ToString("D"));
tsvWriter.WriteLine();
}
tsvWriter.WriteLine();
}
+ private void Write_PlainMetadata( )
+ {
+ string metaPath = Path.Combine(path, plainMetadataFileName);
+
+ using (var metaDataFile = File.Open(metaPath,FileMode.Create)) {
+ using (var mdWriter = new StreamWriter(metaDataFile, Encoding.ASCII))
+ {
+ mdWriter.WriteLine("WorldSeed {0}", ClientAPI.World.Seed);
+ mdWriter.WriteLine("PlayerChunkCoords {0:D} {1:D}", startChunkColumn.X, startChunkColumn.Y);
+ mdWriter.WriteLine("DefaultSpawnPos {0:D} {1:D} {2:D}", ClientAPI.World.DefaultSpawnPosition.AsBlockPos.X,ClientAPI.World.DefaultSpawnPosition.AsBlockPos.Y,ClientAPI.World.DefaultSpawnPosition.AsBlockPos.Z);
+ //mdWriter.WriteLine("CurrentPlayerSpawn", ClientAPI.World.Player.WorldData.EntityPlayer.);
+ mdWriter.WriteLine("ChunkSize {0}", chunkSize);
+ mdWriter.WriteLine("SeaLevel {0:D}", ClientAPI.World.SeaLevel);
+ mdWriter.WriteLine("WorldSize {0:D} {1:D} {2:D}", ClientAPI.World.BulkBlockAccessor.MapSizeX, ClientAPI.World.BulkBlockAccessor.MapSizeY,ClientAPI.World.BulkBlockAccessor.MapSizeZ);
+ mdWriter.WriteLine("RegionSize {0:D}", ClientAPI.World.BulkBlockAccessor.RegionSize);
+ mdWriter.WriteLine("AMVersion '{0}'", ClientAPI.Self().Info.Version);
+ mdWriter.WriteLine("PlayTime {0:F1}", ClientAPI.InWorldEllapsedMilliseconds / 1000);
+ mdWriter.WriteLine("GameDate {0}", ClientAPI.World.Calendar.PrettyDate());
+ mdWriter.WriteLine("Chunks {0:D}", chunkTopMetadata.Count);
+ mdWriter.WriteLine("Chunks Updated {0:D}", updatedChunksTotal);
+ mdWriter.WriteLine("Null Chunks {0:D}", nullChunkCount);
+ mdWriter.Flush( );
+ }
+ }
+ }
+
+
private ColumnMeta CreateColumnMetadata(KeyValuePair<Vec2i, ColumnCounter> mostActiveCol, IMapChunk mapChunk)
{
ColumnMeta data = new ColumnMeta(mostActiveCol.Key.Copy(), ClientAPI, (byte) chunkSize, (ClientAPI.World.BlockAccessor.MapSizeY / chunkSize));
if (worldChunk.IsPacked())
{
+ #if DEBUG
Logger.VerboseDebug("WORLD chunk: Compressed: X{0} Y{1} Z{2}", key.X, targetChunkY, key.Y);
+ #endif
worldChunk.Unpack( );//RESEARCH: Thread Unsafe?
}
foreach (var blockEnt in worldChunk.BlockEntities)
{
- if (blockEnt.Value != null && blockEnt.Value.Block != null && BlockID_Designators.ContainsKey(blockEnt.Value.Block.BlockId))
+ if (blockEnt.Key != null && blockEnt.Value != null && blockEnt.Value.Block != null && BlockID_Designators.ContainsKey(blockEnt.Value.Block.BlockId))
{
var designator = BlockID_Designators[blockEnt.Value.Block.BlockId];
- designator.SpecialAction(ClientAPI, POIs, blockEnt.Value.Pos.Copy(), blockEnt.Value.Block);
+ designator?.SpecialAction(ClientAPI, POIs, blockEnt.Value.Pos.Copy(), blockEnt.Value.Block);
}
}
}
//First Chance fail-safe;
if (worldChunk.Blocks == null || worldChunk.Blocks.Length <= 0) {
+ #if DEBUG
Logger.VerboseDebug("WORLD chunk; Missing block DATA⁈ X{0} Y{1} Z{2} ⁈", key.X, targetChunkY, key.Y);
+ #endif
nullChunkCount++;
continue;
}
//'Last' Chance fail-safe;
if (worldChunk.Blocks == null || worldChunk.Blocks.Length <= 0) {
+ #if DEBUG
Logger.VerboseDebug("Processing Block: Missing block DATA⁈ X{0} Y{1} Z{2} ⁈", X_index, Y_index, Z_index);
+ #endif
nullChunkCount++;
- goto loop_bustout; ;
+ goto loop_bustout;
}
int aBlockId = worldChunk.Blocks[indicie];
}
loop_bustout:;
}
+ #if DEBUG
Logger.VerboseDebug("COLUMN X{0} Z{1}: {2}, processed.", key.X , key.Y, chunkTally + 1);
+ #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.ToArray())
- {
+ #if DEBUG
+ Logger.Debug("Presently {0} Entities", ClientAPI.World.LoadedEntities.Count);
+ #endif
+
+ var keyList = new long[ClientAPI.World.LoadedEntities.Keys.Count];
+ ClientAPI.World.LoadedEntities.Keys.CopyTo(keyList, 0);
+ //'ElementAt'; worse! instead; walk fixed list...
+ Entity loadedEntity;
+ foreach (var key in keyList)
+ {
+ if (ClientAPI.World.LoadedEntities.TryGetValue(key, out loadedEntity))
+ {
#if DEBUG
//Logger.VerboseDebug($"ENTITY: ({loadedEntity.Value.Code}) = #{loadedEntity.Value.EntityId} {loadedEntity.Value.State} {loadedEntity.Value.LocalPos} <<<<<<<<<<<<");
#endif
- var dMatch = Entity_Designators.SingleOrDefault(se => se.Key.Equals(loadedEntity.Value.Code));
- if (dMatch.Value != null)
- {
- dMatch.Value.SpecialAction(ClientAPI, this.EOIs, loadedEntity.Value.Pos.AsBlockPos.Copy(), loadedEntity.Value);
- }
-
+ var dMatch = Entity_Designators.SingleOrDefault(se => se.Key.Equals(loadedEntity.Code));
+ if (dMatch.Value != null)
+ {
+ dMatch.Value.SpecialAction(ClientAPI, this.EOIs, loadedEntity.Pos.AsBlockPos.Copy( ), loadedEntity);
+ }
+ }
}
-
-
}
private void AddNote(string notation)
if (CurrentState != cmdData.State)
{
CurrentState = cmdData.State;
- AwakenCartographer(0.0f);
+ ThreadDecider(0.0f);
}
break;
AddNote(cmdData.Notation);
break;
}
-#if DEBUG
+
ClientAPI.TriggerChatMessage($"Automap commanded to: {cmdData.State} ");
-#endif
+
}
- #endregion
+#endregion
+ private AChunkRenderer InstantiateChosenRenderer(string rendererName )
+ {
+ Logger.VerboseDebug("Using '{0}' style Shard Renderer", rendererName);
+ switch (rendererName)
+ {
+ case StandardRenderer.Name:
+ return new StandardRenderer(ClientAPI, Logger, this.configuration.SeasonalColors);
+
+ case AlternateRenderer.Name:
+ return new AlternateRenderer(ClientAPI, Logger, this.configuration.SeasonalColors);
+
+ case FlatRenderer.Name:
+ return new FlatRenderer(ClientAPI, Logger, this.configuration.SeasonalColors);
+
+ default:
+ throw new ArgumentOutOfRangeException("rendererName",rendererName,"That value isn't supported or known...");
+ }
+
+ return null;
+ }
}
}