OSDN Git Service

re-load partial map metadata
[automap/automap.git] / Automap / Automap_Internals.cs
1 using System;
2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Collections.ObjectModel;
5 using System.Drawing;
6 using System.Drawing.Imaging;
7 using System.IO;
8 using System.Linq;
9 using System.Text;
10 using System.Text.RegularExpressions;
11 using System.Threading;
12
13 using System.Web.UI;
14
15 using Vintagestory.API.Common;
16 using Vintagestory.API.MathTools;
17
18
19
20 namespace Automap
21 {
22         public partial class AutomapMod
23         {
24                 private Thread cartographer_thread;
25
26                 private const string _mapPath = @"Maps";
27                 private const string _chunkPath = @"Chunks";
28                 private const string _domain = @"automap";
29
30                 private ConcurrentDictionary<Vec2i, uint> columnCounter = new ConcurrentDictionary<Vec2i, uint>(3, 150 );
31                 private ColumnsMetadata chunkTopMetadata;
32
33                 private PointsOfInterest POIs;
34                 private Dictionary<int, Designator> BlockID_Designators;
35
36
37                 private Vec2i startChunkColumn;
38                 private uint lastUpdate;
39
40                 private string path;
41                 private IAsset stylesFile;
42
43                 #region Internals
44                 private void StartAutomap( )
45                 {
46                 path = ClientAPI.GetOrCreateDataPath(_mapPath);
47                 path = ClientAPI.GetOrCreateDataPath(Path.Combine(path, "World_" + ClientAPI.World.Seed));//Add name of World too...'ServerApi.WorldManager.CurrentWorldName'
48
49                 stylesFile = ClientAPI.World.AssetManager.Get(new AssetLocation(_domain, "config/automap_format.css"));
50                 Logger.VerboseDebug("CSS loaded: {0} size: {1}",stylesFile.IsLoaded() ,stylesFile.ToText( ).Length);
51
52                 Prefill_POI_Designators( );
53                 startChunkColumn = new Vec2i((ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.X / ClientAPI.World.BlockAccessor.ChunkSize), (ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Z / ClientAPI.World.BlockAccessor.ChunkSize));
54                 chunkTopMetadata = new ColumnsMetadata(startChunkColumn);
55
56                 Logger.Notification("AUTOMAP Start {0}", startChunkColumn);
57                 Reload_Metadata( );
58
59                 ClientAPI.Event.ChunkDirty += ChunkAChanging;
60
61                 cartographer_thread = new Thread(Cartographer);
62                 cartographer_thread.Name = "Cartographer";
63                 cartographer_thread.Priority = ThreadPriority.Lowest;
64                 cartographer_thread.IsBackground = true;
65
66                 ClientAPI.Event.RegisterGameTickListener(AwakenCartographer, 6000);
67                 }
68
69                 private void ChunkAChanging(Vec3i chunkCoord, IWorldChunk chunk, EnumChunkDirtyReason reason)
70                 {                       
71                 Vec2i topPosition = new Vec2i(chunkCoord.X, chunkCoord.Z);
72
73                         columnCounter.AddOrUpdate(topPosition, 1, (key, colAct) => colAct + 1);
74                 }
75
76                 private void AwakenCartographer(float delayed)
77                 {
78
79                 if (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true) {
80                 #if DEBUG
81                 Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState);
82                 #endif
83
84                 if (cartographer_thread.ThreadState.HasFlag(ThreadState.Unstarted)) {
85                 cartographer_thread.Start( );
86                 }
87                 else if (cartographer_thread.ThreadState.HasFlag(ThreadState.WaitSleepJoin)) {
88                 //Time to (re)write chunk shards
89                 cartographer_thread.Interrupt( );
90                 }
91                 ClientAPI.TriggerChatMessage($"Automap {lastUpdate} changes - MAX (N:{chunkTopMetadata.North_mostChunk},S:{chunkTopMetadata.South_mostChunk},E:{chunkTopMetadata.East_mostChunk}, W:{chunkTopMetadata.West_mostChunk} - TOTAL: {chunkTopMetadata.Count})");
92                 }
93
94                 }
95
96
97                 private void Cartographer( )
98                 {
99         wake:
100                 Logger.VerboseDebug("Cartographer thread awoken");
101
102                 try {
103                 uint ejectedItem = 0;
104                 uint updatedChunks = 0;
105                                                 
106
107                 //-- Should dodge enumerator changing underfoot....at a cost.
108                 if (!columnCounter.IsEmpty) {
109                 var tempSet = columnCounter.ToArray( ).OrderByDescending(kvp => kvp.Value);
110                 foreach (var mostActiveCol in tempSet) {
111
112                 var mapChunk = ClientAPI.World.BlockAccessor.GetMapChunk(mostActiveCol.Key);
113
114                 if (mapChunk == null) {
115                 Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!", mostActiveCol.Key);
116
117                 columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem );
118                 continue;
119                 }
120
121                 string filename = $"{mostActiveCol.Key.X}_{mostActiveCol.Key.Y}.png";
122                 filename = Path.Combine(path, filename);
123
124                 var chunkMeta = UpdateColumnMetadata(mostActiveCol,mapChunk);
125
126                 uint pixels = 0;
127                 var chkImg = GenerateChunkImage(mostActiveCol.Key, mapChunk, out pixels);
128
129                 if (pixels > 0) {
130                 chkImg.Save(filename, ImageFormat.Png);
131                 #if DEBUG
132                 Logger.VerboseDebug("Wrote chunk shard: ({0}) - Edits#:{1}, Pixels#:{2}", mostActiveCol.Key, mostActiveCol.Value, pixels);
133                 #endif
134                 updatedChunks++;
135                 chunkTopMetadata.Update(chunkMeta);
136                 columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
137                 }
138                 else {
139                 columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
140                 Logger.VerboseDebug("Un-painted chunk: ({0}) ", mostActiveCol.Key);
141                 }
142
143                 }
144                 }
145
146                 if (updatedChunks > 0) {
147                 //TODO: ONLY update if chunk bounds have changed!
148                 lastUpdate = updatedChunks;
149                 GenerateMapHTML( );
150                 updatedChunks = 0;
151                 }
152
153                 //Then sleep until interupted again, and repeat
154
155                 Logger.VerboseDebug("Thread '{0}' about to sleep indefinitely.", Thread.CurrentThread.Name);
156
157                 Thread.Sleep(Timeout.Infinite);
158
159                 } catch (ThreadInterruptedException) {
160
161                 Logger.VerboseDebug("Thread '{0}' interupted [awoken]", Thread.CurrentThread.Name);
162                 goto wake;
163
164                 } catch (ThreadAbortException) {
165                 Logger.VerboseDebug("Thread '{0}' aborted.", Thread.CurrentThread.Name);
166
167                 } finally {
168                 Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name);
169                 }
170                 }
171
172         #endregion
173
174
175                 private void Prefill_POI_Designators( )
176                 {
177                 this.POIs = new PointsOfInterest( );
178                 this.BlockID_Designators = new Dictionary<int, Designator>( );
179
180                 //Add special marker types for BlockID's of "Interest", overwrite colour, and method
181
182                 var theDesignators = new List<Designator>{
183                                 DefaultDesignators.Roads,
184                 DefaultDesignators.GroundSigns,
185                 DefaultDesignators.WallSigns,
186                 DefaultDesignators.PostSigns,
187                                 };
188
189                 Install_POI_Designators(theDesignators);
190                 }
191
192                 private void Install_POI_Designators(ICollection<Designator> designators)
193                 {
194                 Logger.VerboseDebug("Connecting {0} configured Designators", designators.Count);
195                 foreach (var designator in designators) {                               
196                         var blockIDs = Helpers.ArbitrarytBlockIdHunter(ClientAPI, designator.Pattern, designator.Material);
197                                 if (blockIDs.Count > 0) { Logger.VerboseDebug("Designator {0} has {1} associated blockIDs", designator.ToString( ), blockIDs.Count); }
198                         foreach (var entry in blockIDs) {
199                         BlockID_Designators.Add(entry.Key, designator);
200                         }
201                 }
202
203                 }
204
205
206
207                 #region COPYPASTA
208                 //TODO: rewrite - with vertical ray caster, down to bottom-most chunk (for object detection...)
209                 //A slightly re-written; ChunkMapLayer :: public int[] GenerateChunkImage(Vec2i chunkPos, IMapChunk mc)
210                 internal Bitmap GenerateChunkImage(Vec2i chunkPos, IMapChunk mc, out uint pixelCount)
211                 {
212                 pixelCount = 0;
213                 BlockPos tmpPos = new BlockPos( );
214                 Vec2i localpos = new Vec2i( );
215                 int chunkSize = ClientAPI.World.BlockAccessor.ChunkSize;
216                 var chunksColumn = new IWorldChunk[ClientAPI.World.BlockAccessor.MapSizeY / chunkSize];
217                 Bitmap chunkImage = new Bitmap(chunkSize, chunkSize, PixelFormat.Format24bppRgb);
218                 int topChunkY = mc.YMax / chunkSize;//Heywaitaminute -- this isn't a highest FEATURE, if Rainmap isn't accurate!
219                                                                                         //Metadata of DateTime chunk was edited, chunk coords.,world-seed? Y-Max feature height
220                                                                                         //Grab a chunk COLUMN... Topmost Y down...
221                 for (int chunkY = 0; chunkY <= topChunkY; chunkY++) {
222                 chunksColumn[chunkY] = ClientAPI.World.BlockAccessor.GetChunk(chunkPos.X, chunkY, chunkPos.Y);
223                 //What to do if chunk is a void? invalid?
224                 }
225
226                 // Prefetch map chunks, in pattern
227                 IMapChunk[ ] mapChunks = new IMapChunk[ ]
228                 {
229                         ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y - 1),
230                         ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y),
231                         ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X, chunkPos.Y - 1)
232                 };
233
234
235                 for (int posIndex = 0; posIndex < (chunkSize * chunkSize); posIndex++) {
236                 int mapY = mc.RainHeightMap[posIndex];
237                 int localChunkY = mapY / chunkSize;
238                 if (localChunkY >= (chunksColumn.Length)) continue;//Out of range!
239
240                 MapUtil.PosInt2d(posIndex, chunkSize, localpos);
241                 int localX = localpos.X;
242                 int localZ = localpos.Y;
243
244                 float b = 1;
245                 int leftTop, rightTop, leftBot;
246
247                 IMapChunk leftTopMapChunk = mc;
248                 IMapChunk rightTopMapChunk = mc;
249                 IMapChunk leftBotMapChunk = mc;
250
251                 int topX = localX - 1;
252                 int botX = localX;
253                 int leftZ = localZ - 1;
254                 int rightZ = localZ;
255
256                 if (topX < 0 && leftZ < 0) {
257                 leftTopMapChunk = mapChunks[0];
258                 rightTopMapChunk = mapChunks[1];
259                 leftBotMapChunk = mapChunks[2];
260                 }
261                 else {
262                 if (topX < 0) {
263                 leftTopMapChunk = mapChunks[1];
264                 rightTopMapChunk = mapChunks[1];
265                 }
266                 if (leftZ < 0) {
267                 leftTopMapChunk = mapChunks[2];
268                 leftBotMapChunk = mapChunks[2];
269                 }
270                 }
271
272                 topX = GameMath.Mod(topX, chunkSize);
273                 leftZ = GameMath.Mod(leftZ, chunkSize);
274
275                 leftTop = leftTopMapChunk == null ? 0 : Math.Sign(mapY - leftTopMapChunk.RainHeightMap[leftZ * chunkSize + topX]);
276                 rightTop = rightTopMapChunk == null ? 0 : Math.Sign(mapY - rightTopMapChunk.RainHeightMap[rightZ * chunkSize + topX]);
277                 leftBot = leftBotMapChunk == null ? 0 : Math.Sign(mapY - leftBotMapChunk.RainHeightMap[leftZ * chunkSize + botX]);
278
279                 float slopeness = (leftTop + rightTop + leftBot);
280
281                 if (slopeness > 0) b = 1.2f;
282                 if (slopeness < 0) b = 0.8f;
283
284                 b -= 0.15f; //Slope boost value 
285
286                 if (chunksColumn[localChunkY] == null) {
287
288                 continue;
289                 }
290
291                 chunksColumn[localChunkY].Unpack( );
292                 int blockId = chunksColumn[localChunkY].Blocks[MapUtil.Index3d(localpos.X, mapY % chunkSize, localpos.Y, chunkSize, chunkSize)];
293
294                 Block block = ClientAPI.World.Blocks[blockId];
295
296                 tmpPos.Set(chunkSize * chunkPos.X + localpos.X, mapY, chunkSize * chunkPos.Y + localpos.Y);
297
298                 int avgCol = block.GetColor(ClientAPI, tmpPos);
299                 int rndCol = block.GetRandomColor(ClientAPI, tmpPos, BlockFacing.UP);
300                 //This is still, an abnormal color - the tint is too blue
301                 int col = ColorUtil.ColorOverlay(avgCol, rndCol, 0.125f);
302                 var packedFormat = ColorUtil.ColorMultiply3Clamped(col, b);
303
304                 Color pixelColor = Color.FromArgb(ColorUtil.ColorB(packedFormat), ColorUtil.ColorG(packedFormat), ColorUtil.ColorR(packedFormat));
305
306                 //============ POI Population =================
307                 if (BlockID_Designators.ContainsKey(blockId)) {
308                 var desig = BlockID_Designators[blockId];
309                 pixelColor = desig.OverwriteColor;
310
311                 if (desig.SpecialAction != null) {              
312                 desig.SpecialAction(ClientAPI, this.POIs, tmpPos, block);
313                 }
314                 }
315
316                 chunkImage.SetPixel(localX, localZ, pixelColor);
317                 pixelCount++;
318                 }
319
320
321                 return chunkImage;
322                 }
323                 #endregion
324
325
326                 private void GenerateMapHTML( )
327                 {
328                 string mapFilename = Path.Combine(path, "Automap.html");
329
330                 int TopNorth = chunkTopMetadata.North_mostChunk;
331                 int TopSouth = chunkTopMetadata.South_mostChunk;
332                 int TopEast = chunkTopMetadata.East_mostChunk;
333                 int TopWest = chunkTopMetadata.West_mostChunk;
334
335                 using (StreamWriter outputText = new StreamWriter(File.Open(mapFilename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))) {
336                 using (HtmlTextWriter tableWriter = new HtmlTextWriter(outputText)) {
337                 tableWriter.BeginRender( );
338                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Html);
339
340                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Head);
341                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Title);
342                 tableWriter.WriteEncodedText("Generated Automap");
343                 tableWriter.RenderEndTag( );
344                 //CSS  style  here
345                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Style);
346                 tableWriter.Write(stylesFile.ToText( ));
347                 tableWriter.RenderEndTag( );//</style>
348
349                 tableWriter.RenderEndTag( );
350
351                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Body);
352                 tableWriter.RenderBeginTag(HtmlTextWriterTag.P);
353                 tableWriter.WriteEncodedText($"Created {DateTimeOffset.UtcNow.ToString("u")}");
354                 tableWriter.RenderEndTag( );
355                 tableWriter.RenderBeginTag(HtmlTextWriterTag.P);
356                 tableWriter.WriteEncodedText($"W:{TopWest}, E: {TopEast}, N:{TopNorth}, S:{TopSouth} ");
357                 tableWriter.RenderEndTag( );
358                 tableWriter.WriteLine( );
359                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Table);
360                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Caption);
361                 tableWriter.WriteEncodedText($"Start: {startChunkColumn}, Seed: {ClientAPI.World.Seed}\n");             
362                 tableWriter.RenderEndTag( );
363
364                 //################ X-Axis <thead> #######################
365                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Thead);
366                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
367
368                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Th);
369                 tableWriter.Write("N, W");
370                 tableWriter.RenderEndTag( );
371
372                 for (int xAxisT = TopWest; xAxisT <= TopEast; xAxisT++) {
373                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Th);
374                 tableWriter.Write(xAxisT);
375                 tableWriter.RenderEndTag( );
376                 }
377
378                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Th);
379                 tableWriter.Write("N, E");
380                 tableWriter.RenderEndTag( );
381                 
382                 tableWriter.RenderEndTag( );
383                 tableWriter.RenderEndTag( );
384                 //###### </thead> ################################
385
386                 //###### <tbody> - Chunk rows & Y-axis cols
387                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tbody);
388
389                 //######## <tr> for every vertical row
390                 for (int yAxis = TopNorth; yAxis <= TopSouth; yAxis++) {
391                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
392                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
393                 tableWriter.Write(yAxis);//legend: Y-axis
394                 tableWriter.RenderEndTag( );
395
396                 for (int xAxis = TopWest; xAxis <= TopEast; xAxis++) {
397                 //###### <td>  #### for chunk shard 
398                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
399                 var colLoc = new Vec2i(xAxis, yAxis);
400                 if (chunkTopMetadata.Contains( colLoc)){
401                 ColumnMeta meta = chunkTopMetadata[colLoc];
402                 //Tooltip first                                 
403                 tableWriter.AddAttribute(HtmlTextWriterAttribute.Class, "tooltip");
404                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Div);
405
406                 tableWriter.AddAttribute(HtmlTextWriterAttribute.Src, $"{xAxis}_{yAxis}.png");          
407                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Img);
408                 tableWriter.RenderEndTag( );
409                 // <span class="tooltiptext">Tooltip text
410                 tableWriter.AddAttribute(HtmlTextWriterAttribute.Class, "tooltiptext");
411                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Span);
412
413                 StringBuilder tooltipText = new StringBuilder( );
414                 tooltipText.Append($"{meta.Location.PrettyCoords(ClientAPI)} ");
415                 tooltipText.Append($" Max-Height: {meta.YMax}, Temp: {meta.Temperature.ToString("F1")} " );
416                 tooltipText.Append($" Rainfall: {meta.Rainfall.ToString("F1")}, ");
417                 tooltipText.Append($" Shrubs: {meta.ShrubDensity.ToString("F1")}, ");
418                 tooltipText.Append($" Forest: {meta.ForestDensity.ToString("F1")}, ");
419                 tooltipText.Append($" Fertility: {meta.Fertility.ToString("F1")}, ");
420
421                 if (meta.RockRatio != null) {
422                 foreach (KeyValuePair<int, uint> blockID in meta.RockRatio) {
423                 var block = ClientAPI.World.GetBlock(blockID.Key);
424                 tooltipText.AppendFormat(" {0} × {1},\t", block.Code.GetName( ), meta.RockRatio[blockID.Key]);
425                 }
426                 }
427
428                 tableWriter.WriteEncodedText(tooltipText.ToString() );
429                 
430                 tableWriter.RenderEndTag( );//</span>
431                                                                                 
432
433                 tableWriter.RenderEndTag( );//</div> --tooltip enclosure
434                 }
435                 else {
436                 tableWriter.Write("?");
437                 }       
438
439                 tableWriter.RenderEndTag( );
440                 }//############ </td> ###########
441
442                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
443                 tableWriter.Write(yAxis);//legend: Y-axis
444                 tableWriter.RenderEndTag( );
445
446                 tableWriter.RenderEndTag( );
447                 
448                 }
449                 tableWriter.RenderEndTag( );
450
451                 //################ X-Axis <tfoot> #######################
452                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tfoot);
453                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
454
455                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
456                 tableWriter.Write("S, W");
457                 tableWriter.RenderEndTag( );
458
459                 for (int xAxisB = TopWest; xAxisB <= TopEast; xAxisB++) {
460                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
461                 tableWriter.Write(xAxisB);
462                 tableWriter.RenderEndTag( );
463                 }
464
465                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
466                 tableWriter.Write("S, E");
467                 tableWriter.RenderEndTag( );
468
469                 tableWriter.RenderEndTag( );
470                 tableWriter.RenderEndTag( );
471                 //###### </tfoot> ################################
472
473
474                 tableWriter.RenderEndTag( );//</table>
475                 
476                 //############## POI list #####################
477                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Ul);
478                 foreach (var poi in this.POIs) {
479                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Li);
480                 tableWriter.WriteEncodedText(poi.Timestamp.ToString("u"));
481                 tableWriter.WriteEncodedText(poi.Notes);
482                 tableWriter.WriteEncodedText(poi.Location.PrettyCoords(this.ClientAPI));
483                 tableWriter.RenderEndTag( );
484                 }
485
486                 tableWriter.RenderEndTag( );
487                 tableWriter.RenderEndTag( );
488                 
489
490                 tableWriter.EndRender( );
491                 tableWriter.Flush( );
492                 }
493                 outputText.Flush( );            
494                 }
495
496                 Logger.VerboseDebug("Generated HTML map");
497                 }
498
499
500
501                 private ColumnMeta UpdateColumnMetadata(KeyValuePair<Vec2i, uint> mostActiveCol, IMapChunk mapChunk)
502                 {
503                 ColumnMeta data = new ColumnMeta(mostActiveCol.Key.Copy());
504                 BlockPos equivBP = new BlockPos(mostActiveCol.Key.X * ClientAPI.World.BlockAccessor.ChunkSize,
505                                                                                 mapChunk.YMax,
506                                                                                 mostActiveCol.Key.Y * ClientAPI.World.BlockAccessor.ChunkSize);
507
508                 var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP);              
509                 data.Temperature = climate.Temperature;
510                 data.Fertility = climate.Fertility;
511                 data.ForestDensity = climate.ForestDensity;
512                 data.Rainfall = climate.Rainfall;
513                 data.ShrubDensity = climate.ShrubDensity;
514
515                 data.YMax = mapChunk.YMax;
516
517                 
518                 /* Only present on server....
519                 if (mapChunk.TopRockIdMap != null) {
520                 foreach (var topRockId in mapChunk.TopRockIdMap) {
521
522                 if (data.RockRatio.ContainsKey(topRockId)) { data.RockRatio[topRockId]++; }
523                 else { data.RockRatio.Add(topRockId, 1); }
524                 }
525                 }*/
526
527
528                 return data;
529                 }
530
531                 /// <summary>
532                 /// Reload chunk bounds from chunk shards
533                 /// </summary>
534                 /// <returns>The metadata.</returns>
535                 private void Reload_Metadata( )
536                 {
537                 string chunkFile_filter = @"*_*.png";
538                 Regex chunkShardRegex = new Regex(@"(?<X>[\d]+)_(?<Z>[\d]+).png", RegexOptions.Singleline);
539
540                 var worldmapDir = new DirectoryInfo(path);
541
542                 if (worldmapDir.Exists) {
543
544                 var files = worldmapDir.GetFiles(chunkFile_filter);
545
546                 if (files.Length > 0) {
547                 #if DEBUG
548                 Logger.VerboseDebug("{0} Existing world chunk shards", files.Length);
549                 #endif
550
551                 foreach (var shardFile in files) {
552                 var result = chunkShardRegex.Match(shardFile.Name);
553                 if (result.Success) {
554                 int X_chunk_pos = int.Parse(result.Groups["X"].Value );
555                 int Z_chunk_pos = int.Parse(result.Groups["Z"].Value );
556                 //TODO: METADATA from shard
557                 chunkTopMetadata.Add(new ColumnMeta(new Vec2i(X_chunk_pos, Z_chunk_pos)));
558                 }
559                 }
560
561                 }
562                 }
563                 else {
564                 #if DEBUG
565                 Logger.VerboseDebug("Could not open world map directory");
566                 #endif
567                 }
568
569
570
571                 }
572         }
573
574 }