OSDN Git Service

73fc20308345d714ede912c6f19a8b4b478f15d8
[automap/automap.git] / Automap / AutomapMod.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Collections.ObjectModel;
4 using System.IO;
5 using System.Threading;
6 using System.Drawing;
7 using System.Drawing.Imaging;
8 using System.Linq;
9
10
11 using Vintagestory.API.Client;
12 using Vintagestory.API.Common;
13 using Vintagestory.API.Common.Entities;
14 using Vintagestory.API.Config;
15 using Vintagestory.API.MathTools;
16 using Vintagestory.API.Server;
17 using Vintagestory.GameContent;
18 using Vintagestory.API.Datastructures;
19
20
21 namespace Automap
22 {
23         public class AutomapMod : ModSystem
24         {
25                 private ICoreAPI API { get; set; }
26                 private ICoreClientAPI ClientAPI { get; set; }
27                 private ILogger Logger { get; set; }
28
29                 private const string _mapPath = @"Maps";
30                 private const string _chunkPath = @"Chunks";
31                 private readonly object colLock = new object( );
32                 private Dictionary<Vec2i, uint> columnCounter = new Dictionary<Vec2i, uint>();
33                 private HashSet<Vec2i> knownChunkTops = new HashSet<Vec2i>();
34                 private Vec2i startPosition;
35
36                 private Thread cartographer_thread;
37
38                 public override bool ShouldLoad(EnumAppSide forSide)
39                 {
40                         return forSide.IsClient();
41                 }
42
43                 public override void StartClientSide(ICoreClientAPI api)
44                 {                       
45                         this.API = api;
46
47                         if (api.Side == EnumAppSide.Client) {
48                         this.ClientAPI = api as ICoreClientAPI;
49                         this.Logger = Mod.Logger;
50                         
51                         ClientAPI.Event.LevelFinalize += StartAutomap;
52                         }
53
54                         base.StartClientSide(api);
55                 }
56
57                 public override double ExecuteOrder( )
58                 {
59                 return 0.2;
60                 }
61
62
63
64                 #region Internals
65                 private void StartAutomap( )
66                 {
67                 Mod.Logger.Notification("AUTOMAP SETUP");
68                 startPosition = new Vec2i(ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.X, ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Z);
69                 ClientAPI.Event.ChunkDirty += ChunkAChanging;
70                 
71                 cartographer_thread = new Thread(Cartographer);
72                 cartographer_thread.Name = "Cartographer";
73                 cartographer_thread.Priority = ThreadPriority.Lowest;
74                 cartographer_thread.IsBackground = true;
75
76                 ClientAPI.Event.RegisterGameTickListener(AwakenCartographer, 6000);
77                 }
78
79                 private void AwakenCartographer(float delayed)
80                 {
81
82                 if (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true) 
83                 {               
84                 #if DEBUG
85                         Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState);
86                 #endif
87
88                         if (cartographer_thread.ThreadState.HasFlag(ThreadState.Unstarted)) 
89                         {                                       
90                         cartographer_thread.Start( );
91                         }
92                         else if (cartographer_thread.ThreadState.HasFlag(ThreadState.WaitSleepJoin)) {          
93                         //Time to (re)write chunk shards
94                         cartographer_thread.Interrupt( );
95                         }
96                 }
97
98                 }
99
100
101                 private void Cartographer( )
102                 {
103         wake:
104                 Logger.VerboseDebug("Cartographer thread awoken");
105
106                 try {
107
108                         while (columnCounter.Count > 0) 
109                         {
110                         var mostActiveCol = columnCounter.OrderByDescending(kvp => kvp.Value).First();
111                         var mapChunk = ClientAPI.World.BlockAccessor.GetMapChunk(mostActiveCol.Key);
112                         Logger.VerboseDebug("Selected: ({0}) - Edits#:{1}", mostActiveCol.Key, mostActiveCol.Value);
113
114                         if (mapChunk == null) {
115                                 Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!",mostActiveCol.Key);
116                                 columnCounter.Remove(mostActiveCol.Key);
117                                 continue;
118                         }
119
120                         string filename = $"{mostActiveCol.Key.X}_{mostActiveCol.Key.Y}.png";
121
122                         filename = Path.Combine(ClientAPI.GetOrCreateDataPath(_mapPath), filename);
123
124                         var chkImg = GenerateChunkImage(mostActiveCol.Key, mapChunk);
125                         
126                         chkImg.Save(filename, ImageFormat.Png);
127
128                         knownChunkTops.Add(mostActiveCol.Key);
129                         columnCounter.Remove(mostActiveCol.Key);                        
130                         } 
131
132
133
134                 //Then sleep until interupted again, and repeat
135
136                 Logger.VerboseDebug("Thread '{0}' about to sleep indefinitely.", Thread.CurrentThread.Name);
137
138                 Thread.Sleep(Timeout.Infinite);
139
140                 } catch (ThreadInterruptedException) {
141
142                 Logger.VerboseDebug("Thread '{0}' interupted [awoken]", Thread.CurrentThread.Name);
143                 goto wake;
144
145                 } catch (ThreadAbortException) {
146                 Logger.VerboseDebug("Thread '{0}' aborted.", Thread.CurrentThread.Name);
147
148                 } finally {
149                 Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name);
150                 }
151                 }
152                 #endregion
153
154
155
156                 private void ChunkAChanging(Vec3i chunkCoord, IWorldChunk chunk, EnumChunkDirtyReason reason)
157                 {
158                 //Logger.VerboseDebug($"Change: @({chunkCoord}) R: {reason}");
159                 Vec2i topPosition = new Vec2i(chunkCoord.X, chunkCoord.Z);
160                 
161                         lock (colLock)
162                         {
163                         if (columnCounter.ContainsKey(topPosition)) { columnCounter[topPosition]++; } else { columnCounter.Add(topPosition, 1); }
164                         }
165
166                 }
167
168                 private void print_stats(float interval)
169                 {
170                 if (columnCounter != null && columnCounter.Count > 0) {
171                 foreach (var count in columnCounter) {
172                 Logger.VerboseDebug($"({count.Key}): {count.Value}");
173                 }
174                 }
175
176                 }
177
178
179                 #region COPYPASTA
180                 public Bitmap GenerateChunkImage(Vec2i chunkPos, IMapChunk mc)
181                 {
182                 BlockPos tmpPos = new BlockPos( );
183                 Vec2i localpos = new Vec2i( );          
184                 int chunkSize = ClientAPI.World.BlockAccessor.ChunkSize;
185                 var chunksColumn = new IWorldChunk[ClientAPI.World.BlockAccessor.MapSizeY / chunkSize];
186                 Bitmap chunkImage = new Bitmap(chunkSize, chunkSize, PixelFormat.Format24bppRgb );
187                 int topChunkY = mc.YMax / chunkSize;//Heywaitaminute -- this isn't a highest FEATURE, if Rainmap isn't accurate!
188                  //Metadata of DateTime chunk was edited, chunk coords.,world-seed? Y-Max feature height
189                 //Grab a chunk COLUMN... Topmost Y down...
190                 for (int chunkY = 0; chunkY < topChunkY; chunkY++) {
191                 chunksColumn[chunkY] = ClientAPI.World.BlockAccessor.GetChunk(chunkPos.X, chunkY, chunkPos.Y);
192                 //What to do if chunk is a void? invalid?
193                 }
194                 
195                 // Prefetch map chunks, in pattern
196                 IMapChunk[ ] mapChunks = new IMapChunk[ ]
197                 {
198                         ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y - 1),
199                         ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y),
200                         ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X, chunkPos.Y - 1)
201                 };
202                                         
203
204                 for (int posIndex = 0; posIndex < (chunkSize * chunkSize); posIndex++) {
205                 int mapY = mc.RainHeightMap[posIndex];
206                 int localChunkY = mapY / chunkSize;
207                 if (localChunkY >= (chunksColumn.Length ) ) continue;//Out of range!
208
209                 MapUtil.PosInt2d(posIndex, chunkSize, localpos);
210                 int localX = localpos.X;
211                 int localZ = localpos.Y;
212
213                 float b = 1;
214                 int leftTop, rightTop, leftBot;
215
216                 IMapChunk leftTopMapChunk = mc;
217                 IMapChunk rightTopMapChunk = mc;
218                 IMapChunk leftBotMapChunk = mc;
219
220                 int topX = localX - 1;
221                 int botX = localX;
222                 int leftZ = localZ - 1;
223                 int rightZ = localZ;
224
225                 if (topX < 0 && leftZ < 0) {
226                 leftTopMapChunk = mapChunks[0];
227                 rightTopMapChunk = mapChunks[1];
228                 leftBotMapChunk = mapChunks[2];
229                 }
230                 else {
231                 if (topX < 0) {
232                 leftTopMapChunk = mapChunks[1];
233                 rightTopMapChunk = mapChunks[1];
234                 }
235                 if (leftZ < 0) {
236                 leftTopMapChunk = mapChunks[2];
237                 leftBotMapChunk = mapChunks[2];
238                 }
239                 }
240
241                 topX = GameMath.Mod(topX, chunkSize);
242                 leftZ = GameMath.Mod(leftZ, chunkSize);
243
244                 leftTop = leftTopMapChunk == null ? 0 : Math.Sign(mapY - leftTopMapChunk.RainHeightMap[leftZ * chunkSize + topX]);
245                 rightTop = rightTopMapChunk == null ? 0 : Math.Sign(mapY - rightTopMapChunk.RainHeightMap[rightZ * chunkSize + topX]);
246                 leftBot = leftBotMapChunk == null ? 0 : Math.Sign(mapY - leftBotMapChunk.RainHeightMap[leftZ * chunkSize + botX]);
247
248                 float slopeness = (leftTop + rightTop + leftBot);
249
250                 if (slopeness > 0) b = 1.2f;
251                 if (slopeness < 0) b = 0.8f;
252
253                 b -= 0.15f; // Map seems overally a bit too bright
254                                         //b = 1;
255                 if (chunksColumn[localChunkY] == null) {
256                         
257                 continue;
258                 }
259
260                 chunksColumn[localChunkY].Unpack( );
261                 int blockId = chunksColumn[localChunkY].Blocks[MapUtil.Index3d(localpos.X, mapY % chunkSize, localpos.Y, chunkSize, chunkSize)];
262                 Block block = ClientAPI.World.Blocks[blockId];
263
264                 tmpPos.Set(chunkSize * chunkPos.X + localpos.X, mapY, chunkSize * chunkPos.Y + localpos.Y);
265
266                 int avgCol = block.GetColor(ClientAPI, tmpPos);
267                 int rndCol = block.GetRandomColor(ClientAPI, tmpPos, BlockFacing.UP);
268                 //Merge color?
269                 int col = ColorUtil.ColorOverlay(avgCol, rndCol, 0.25f);
270                 var packedFormat = ColorUtil.ColorMultiply3Clamped(col, b) | 255 << 24;//Is the Struct, truly so undesirable?
271
272                 Color pixelColor = Color.FromArgb(ColorUtil.ColorR(packedFormat), ColorUtil.ColorG(packedFormat), ColorUtil.ColorB(packedFormat));
273                 chunkImage.SetPixel(localX,localZ, pixelColor);
274                 }
275
276
277                 return chunkImage;
278                 }
279                 #endregion
280         }
281 }
282