• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#objective-cqt誰得windowscocoapythonphprubygameguibathyscaphec翻訳omegat計画中(planning stage)frameworktwittertestdomvb.netdirectxbtronarduinopreviewerゲームエンジン

Automap (client) [VS plugin mod]


Commit MetaInfo

Revision6466988389eaf0f3502714abeb8a8d5031c3a6cf (tree)
Time2020-01-11 07:05:46
Authormelchior <melchior@user...>
Commitermelchior

Log Message

W.I.P. #6: GUI incomplete, injected JSON map state on HTML

Change Summary

Incremental Difference

--- a/Automap/Automap.csproj
+++ b/Automap/Automap.csproj
@@ -76,10 +76,11 @@
7676 <Compile Include="Helpers.cs" />
7777 <Compile Include="Data\PointOfInterest.cs" />
7878 <Compile Include="Data\Designator.cs" />
79- <Compile Include="Automap_Internals.cs" />
8079 <Compile Include="Designators\DefaultDesignators.cs" />
8180 <Compile Include="Data\ColumnMeta.cs" />
8281 <Compile Include="Data\PngMetadataChunk.cs" />
82+ <Compile Include="Subsystems\AutomapSystem.cs" />
83+ <Compile Include="Subsystems\AutomapGUIDialog.cs" />
8384 </ItemGroup>
8485 <ItemGroup>
8586 <Folder Include="VS_libs\" />
@@ -89,6 +90,7 @@
8990 <Folder Include="assets\automap\config\" />
9091 <Folder Include="Designators\" />
9192 <Folder Include="Libs\" />
93+ <Folder Include="Subsystems\" />
9294 </ItemGroup>
9395 <ItemGroup>
9496 <None Include="modinfo.json">
--- a/Automap/AutomapMod.cs
+++ b/Automap/AutomapMod.cs
@@ -1,17 +1,17 @@
1-
2-using Vintagestory.API.Client;
1+using Vintagestory.API.Client;
32 using Vintagestory.API.Common;
43
54
65
76 namespace Automap
87 {
9- public partial class AutomapMod : ModSystem
8+ public class AutomapMod : ModSystem
109 {
1110 private ICoreAPI API { get; set; }
1211 private ICoreClientAPI ClientAPI { get; set; }
1312 private ILogger Logger { get; set; }
14-
13+ private AutomapSystem _localAutomap;
14+ private AutomapGUIDialog _automapDialog;
1515
1616
1717 public override bool ShouldLoad(EnumAppSide forSide)
@@ -27,8 +27,13 @@ namespace Automap
2727 this.ClientAPI = api as ICoreClientAPI;
2828 this.Logger = Mod.Logger;
2929
30+
3031 ClientAPI.Logger.VerboseDebug("Automap Present!");
31- ClientAPI.Event.LevelFinalize += StartAutomap;
32+ _localAutomap = new AutomapSystem( this.ClientAPI, this.Logger);
33+ _automapDialog = new AutomapGUIDialog(ClientAPI, _localAutomap);
34+
35+ ClientAPI.Input.RegisterHotKey(AutomapGUIDialog._automapControlPanelKey, "Automap control panel", GlKeys.A, HotkeyType.GUIOrOtherControls);
36+ ClientAPI.Input.SetHotKeyHandler(AutomapGUIDialog._automapControlPanelKey, ToggleAM_Dialog);
3237 }
3338
3439 base.StartClientSide(api);
@@ -39,7 +44,13 @@ namespace Automap
3944 return 0.2;
4045 }
4146
47+ private bool ToggleAM_Dialog(KeyCombination comb)
48+ {
49+ if (_automapDialog.IsOpened( )) _automapDialog.TryClose( );
50+ else _automapDialog.TryOpen( );
4251
52+ return true;
53+ }
4354
4455
4556 }
--- a/Automap/Data/Designator.cs
+++ b/Automap/Data/Designator.cs
@@ -20,6 +20,7 @@ namespace Automap
2020 public DesignatonAction SpecialAction;
2121 public AssetLocation Pattern;
2222 public EnumBlockMaterial? Material;
23+ public bool Enabled { get; set; }
2324
2425 private Designator( )
2526 {
@@ -30,7 +31,8 @@ namespace Automap
3031 {
3132 this.Pattern = pattern;
3233 this.OverwriteColor = overwriteColor;
33- Material = material;
34+ this.Material = material;
35+ this.Enabled = true;
3436 }
3537
3638 public Designator(AssetLocation pattern, Color overwriteColor, EnumBlockMaterial? material ,DesignatonAction specialAct )
@@ -39,6 +41,7 @@ namespace Automap
3941 this.OverwriteColor = overwriteColor;
4042 this.Material = material;
4143 this.SpecialAction = specialAct;
44+ this.Enabled = true;
4245 }
4346
4447 public override string ToString( )
--- /dev/null
+++ b/Automap/Subsystems/AutomapGUIDialog.cs
@@ -0,0 +1,78 @@
1+using System;
2+
3+
4+using Vintagestory.API.Client;
5+using Vintagestory.API.Common;
6+
7+namespace Automap
8+{
9+ public class AutomapGUIDialog : GuiDialog
10+ {
11+ public const string _automapControlPanelKey = "automapControlPanelKey";
12+
13+ private ILogger Logger;
14+ private AutomapSystem _automapSystem;
15+
16+ public override string ToggleKeyCombinationCode {
17+ get
18+ {
19+ return _automapControlPanelKey;
20+ }
21+ }
22+
23+ public AutomapGUIDialog(ICoreClientAPI capi,AutomapSystem ams) : base(capi)
24+ {
25+ _automapSystem = ams;
26+ Logger = capi.Logger;
27+ SetupDialog( );
28+ }
29+
30+ private void SetupDialog( )
31+ {
32+ ElementBounds dialogBounds = ElementStdBounds.AutosizedMainDialog.WithAlignment(EnumDialogArea.CenterMiddle);
33+
34+ ElementBounds textBounds = ElementBounds.Fixed(0, 40, 500, 300);
35+
36+ ElementBounds bgBounds = ElementBounds.Fill.WithFixedPadding(GuiStyle.ElementToDialogPadding);
37+ bgBounds.BothSizing = ElementSizing.FitToChildren;
38+ bgBounds.WithChildren(textBounds);
39+
40+ ElementBounds toggleBounds = textBounds.CopyOffsetedSibling(3, 5, 5, 2);
41+ toggleBounds.fixedHeight = 18;
42+ toggleBounds.fixedWidth = 50;
43+
44+ ElementBounds txtStatusBounds = textBounds.CopyOffsetedSibling(0, 20, 2, 4);
45+ txtStatusBounds.fixedHeight = 16;
46+ txtStatusBounds.percentWidth = 1;
47+
48+
49+ this.SingleComposer = capi.Gui.CreateCompo("automapControlPanel", dialogBounds)
50+ .AddShadedDialogBG(bgBounds)
51+ .AddDialogTitleBar("Automap Controls", OnTitleBarCloseClicked)
52+ .AddStaticText("Configure Automap settings:", CairoFont.WhiteDetailText( ), textBounds)
53+ .AddToggleButton("Run", CairoFont.ButtonText( ), RunToggle, toggleBounds, "btnRun")
54+ .AddStaticText("Idle.", CairoFont.WhiteSmallText( ).WithFontSize(12), txtStatusBounds, "txtStatus")
55+ .Compose( );
56+ }
57+
58+ private void OnTitleBarCloseClicked( )
59+ {
60+ TryClose( );
61+ }
62+
63+ /// <summary>
64+ /// Toggle Automap from/to RUN state
65+ /// </summary>
66+ /// <returns>The toggle.</returns>
67+ /// <param name="t1">T1.</param>
68+ internal void RunToggle(bool toggle)
69+ {
70+ _automapSystem.Enabled = toggle;
71+ Logger.VerboseDebug("Dialog Changed; [ Automap Enabled: {0} ]", toggle);
72+ var statusText = this.SingleComposer.GetStaticText("txtStatus");
73+ statusText.SetValue($"Running: {this._automapSystem.Enabled}, Total: {this._automapSystem.updatedChunksTotal}, Nulls: {this._automapSystem.nullChunkCount} " );
74+
75+ }
76+ }
77+}
78+
--- a/Automap/Automap_Internals.cs
+++ b/Automap/Subsystems/AutomapSystem.cs
@@ -13,6 +13,7 @@ using System.Web.UI;
1313 using Hjg.Pngcs;
1414 using Hjg.Pngcs.Chunks;
1515
16+using Vintagestory.API.Client;
1617 using Vintagestory.API.Common;
1718 using Vintagestory.API.MathTools;
1819
@@ -20,32 +21,44 @@ using Vintagestory.API.MathTools;
2021
2122 namespace Automap
2223 {
23- public partial class AutomapMod
24+ public class AutomapSystem
2425 {
2526 private Thread cartographer_thread;
27+ private ICoreClientAPI ClientAPI { get; set; }
28+ private ILogger Logger { get; set; }
2629
2730 private const string _mapPath = @"Maps";
2831 private const string _chunkPath = @"Chunks";
2932 private const string _domain = @"automap";
30-
3133 private const string chunkFile_filter = @"*_*.png";
3234 private static Regex chunkShardRegex = new Regex(@"(?<X>[\d]+)_(?<Z>[\d]+).png", RegexOptions.Singleline);
3335
3436 private ConcurrentDictionary<Vec2i, uint> columnCounter = new ConcurrentDictionary<Vec2i, uint>(3, 150 );
3537 private ColumnsMetadata chunkTopMetadata;
36-
3738 private PointsOfInterest POIs;
38- private Dictionary<int, Designator> BlockID_Designators;
3939
40+ internal Dictionary<int, Designator> BlockID_Designators { get; private set;}
41+ internal bool Enabled { get; set; }
42+ //Run status, Chunks processed, stats, center of map....
43+ internal uint nullChunkCount;
44+ internal uint updatedChunksTotal;
45+ internal Vec2i startChunkColumn;
4046
41- private Vec2i startChunkColumn;
42- private uint lastUpdate;
4347
4448 private string path;
4549 private IAsset stylesFile;
4650
51+
52+ public AutomapSystem(ICoreClientAPI clientAPI, ILogger logger)
53+ {
54+ this.ClientAPI = clientAPI;
55+ this.Logger = logger;
56+ ClientAPI.Event.LevelFinalize += EngageAutomap;
57+ }
58+
59+
4760 #region Internals
48- private void StartAutomap( )
61+ private void EngageAutomap( )
4962 {
5063 path = ClientAPI.GetOrCreateDataPath(_mapPath);
5164 path = ClientAPI.GetOrCreateDataPath(Path.Combine(path, "World_" + ClientAPI.World.Seed));//Add name of World too...'ServerApi.WorldManager.CurrentWorldName'
@@ -80,7 +93,7 @@ namespace Automap
8093 private void AwakenCartographer(float delayed)
8194 {
8295
83- if (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true) {
96+ if (Enabled && (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true)) {
8497 #if DEBUG
8598 Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState);
8699 #endif
@@ -92,7 +105,7 @@ namespace Automap
92105 //Time to (re)write chunk shards
93106 cartographer_thread.Interrupt( );
94107 }
95- 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})");
108+ 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})");
96109 }
97110
98111 }
@@ -106,7 +119,6 @@ namespace Automap
106119 try {
107120 uint ejectedItem = 0;
108121 uint updatedChunks = 0;
109-
110122
111123 //-- Should dodge enumerator changing underfoot....at a cost.
112124 if (!columnCounter.IsEmpty) {
@@ -117,7 +129,7 @@ namespace Automap
117129
118130 if (mapChunk == null) {
119131 Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!", mostActiveCol.Key);
120-
132+ nullChunkCount++;
121133 columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem );
122134 continue;
123135 }
@@ -147,7 +159,7 @@ namespace Automap
147159
148160 if (updatedChunks > 0) {
149161 //TODO: ONLY update if chunk bounds have changed!
150- lastUpdate = updatedChunks;
162+ updatedChunksTotal += updatedChunks;
151163 GenerateMapHTML( );
152164 updatedChunks = 0;
153165 }
@@ -171,7 +183,7 @@ namespace Automap
171183 }
172184 }
173185
174- #endregion
186+
175187
176188
177189 private void Prefill_POI_Designators( )
@@ -205,137 +217,6 @@ namespace Automap
205217 }
206218
207219
208-
209- #region COPYPASTA
210- //TODO: rewrite - with vertical ray caster, down to bottom-most chunk (for object detection...)
211- //A partly re-written; ChunkMapLayer :: public int[] GenerateChunkImage(Vec2i chunkPos, IMapChunk mc)
212- internal void GenerateChunkImage(Vec2i chunkPos, IMapChunk mc, PngWriter pngWriter, out uint pixelCount)
213- {
214- pixelCount = 0;
215- BlockPos tmpPos = new BlockPos( );
216- Vec2i localpos = new Vec2i( );
217- int chunkSize = ClientAPI.World.BlockAccessor.ChunkSize;
218- var chunksColumn = new IWorldChunk[ClientAPI.World.BlockAccessor.MapSizeY / chunkSize];
219-
220- int topChunkY = mc.YMax / chunkSize;//Heywaitaminute -- this isn't a highest FEATURE, if Rainmap isn't accurate!
221- //Metadata of DateTime chunk was edited, chunk coords.,world-seed? Y-Max feature height
222- //Grab a chunk COLUMN... Topmost Y down...
223- for (int chunkY = 0; chunkY <= topChunkY; chunkY++) {
224- chunksColumn[chunkY] = ClientAPI.World.BlockAccessor.GetChunk(chunkPos.X, chunkY, chunkPos.Y);
225- //What to do if chunk is a void? invalid?
226- }
227-
228- // Prefetch map chunks, in pattern
229- IMapChunk[ ] mapChunks = new IMapChunk[ ]
230- {
231- ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y - 1),
232- ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y),
233- ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X, chunkPos.Y - 1)
234- };
235-
236- //pre-create PNG line slices...
237- ImageLine[ ] lines = Enumerable.Repeat(new object(), chunkSize).Select(l => new ImageLine(pngWriter.ImgInfo)).ToArray();
238-
239- for (int posIndex = 0; posIndex < (chunkSize * chunkSize); posIndex++) {
240- int mapY = mc.RainHeightMap[posIndex];
241- int localChunkY = mapY / chunkSize;
242- if (localChunkY >= (chunksColumn.Length)) continue;//Out of range!
243-
244- MapUtil.PosInt2d(posIndex, chunkSize, localpos);
245- int localX = localpos.X;
246- int localZ = localpos.Y;
247-
248- float b = 1;
249- int leftTop, rightTop, leftBot;
250-
251- IMapChunk leftTopMapChunk = mc;
252- IMapChunk rightTopMapChunk = mc;
253- IMapChunk leftBotMapChunk = mc;
254-
255- int topX = localX - 1;
256- int botX = localX;
257- int leftZ = localZ - 1;
258- int rightZ = localZ;
259-
260- if (topX < 0 && leftZ < 0) {
261- leftTopMapChunk = mapChunks[0];
262- rightTopMapChunk = mapChunks[1];
263- leftBotMapChunk = mapChunks[2];
264- }
265- else {
266- if (topX < 0) {
267- leftTopMapChunk = mapChunks[1];
268- rightTopMapChunk = mapChunks[1];
269- }
270- if (leftZ < 0) {
271- leftTopMapChunk = mapChunks[2];
272- leftBotMapChunk = mapChunks[2];
273- }
274- }
275-
276- topX = GameMath.Mod(topX, chunkSize);
277- leftZ = GameMath.Mod(leftZ, chunkSize);
278-
279- leftTop = leftTopMapChunk == null ? 0 : Math.Sign(mapY - leftTopMapChunk.RainHeightMap[leftZ * chunkSize + topX]);
280- rightTop = rightTopMapChunk == null ? 0 : Math.Sign(mapY - rightTopMapChunk.RainHeightMap[rightZ * chunkSize + topX]);
281- leftBot = leftBotMapChunk == null ? 0 : Math.Sign(mapY - leftBotMapChunk.RainHeightMap[leftZ * chunkSize + botX]);
282-
283- float slopeness = (leftTop + rightTop + leftBot);
284-
285- if (slopeness > 0) b = 1.2f;
286- if (slopeness < 0) b = 0.8f;
287-
288- b -= 0.15f; //Slope boost value
289-
290- if (chunksColumn[localChunkY] == null) {
291-
292- continue;
293- }
294-
295- chunksColumn[localChunkY].Unpack( );
296- int blockId = chunksColumn[localChunkY].Blocks[MapUtil.Index3d(localpos.X, mapY % chunkSize, localpos.Y, chunkSize, chunkSize)];
297-
298- Block block = ClientAPI.World.Blocks[blockId];
299-
300- tmpPos.Set(chunkSize * chunkPos.X + localpos.X, mapY, chunkSize * chunkPos.Y + localpos.Y);
301-
302- int avgCol = block.GetColor(ClientAPI, tmpPos);
303- int rndCol = block.GetRandomColor(ClientAPI, tmpPos, BlockFacing.UP);
304- int col = ColorUtil.ColorOverlay(avgCol, rndCol, 0.125f);
305- var packedFormat = ColorUtil.ColorMultiply3Clamped(col, b);
306-
307- int red = ColorUtil.ColorB(packedFormat);
308- int green = ColorUtil.ColorG(packedFormat);
309- int blue = ColorUtil.ColorR(packedFormat);
310-
311-
312- //============ POI Population =================
313- if (BlockID_Designators.ContainsKey(blockId)) {
314- var desig = BlockID_Designators[blockId];
315- red = desig.OverwriteColor.R;
316- green = desig.OverwriteColor.G;
317- blue = desig.OverwriteColor.B;
318-
319- if (desig.SpecialAction != null) {
320- desig.SpecialAction(ClientAPI, this.POIs, tmpPos, block);
321- }
322- }
323-
324- ImageLineHelper.SetPixel(lines[localZ], localX, red, green, blue);
325-
326- //chunkImage.SetPixel(localX, localZ, pixelColor);
327- pixelCount++;
328- }
329-
330- for (int row = 0; row < pngWriter.ImgInfo.Rows; row++) {
331- pngWriter.WriteRow(lines[row], row);
332- }
333-
334- pngWriter.End( );
335- }
336- #endregion
337-
338-
339220 private void GenerateMapHTML( )
340221 {
341222 string mapFilename = Path.Combine(path, "Automap.html");
@@ -359,6 +240,20 @@ namespace Automap
359240 tableWriter.Write(stylesFile.ToText( ));
360241 tableWriter.RenderEndTag( );//</style>
361242
243+ //## JSON map-state data ######################
244+ tableWriter.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
245+ tableWriter.RenderBeginTag(HtmlTextWriterTag.Script);
246+
247+ tableWriter.Write("var available_images = [");
248+
249+ foreach (var shard in this.chunkTopMetadata) {
250+ tableWriter.Write("{{X:{0},Y:{1} }}, ", shard.Location.X, shard.Location.Y);
251+ }
252+
253+ tableWriter.Write(" ];\n");
254+
255+ tableWriter.RenderEndTag( );
256+
362257 tableWriter.RenderEndTag( );
363258
364259 tableWriter.RenderBeginTag(HtmlTextWriterTag.Body);
@@ -497,9 +392,12 @@ namespace Automap
497392 }
498393
499394 tableWriter.RenderEndTag( );
500- tableWriter.RenderEndTag( );
395+
501396
502397
398+
399+ tableWriter.RenderEndTag( );//### </BODY> ###
400+
503401 tableWriter.EndRender( );
504402 tableWriter.Flush( );
505403 }
@@ -518,7 +416,8 @@ namespace Automap
518416 mapChunk.YMax,
519417 mostActiveCol.Key.Y * ClientAPI.World.BlockAccessor.ChunkSize);
520418
521- var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP);
419+ var climate = ClientAPI.World.BlockAccessor.GetClimateAt(equivBP);
420+ data.ChunkAge = TimeSpan.FromHours(ClientAPI.World.Calendar.TotalHours);
522421 data.Temperature = climate.Temperature;
523422 data.Fertility = climate.Fertility;
524423 data.ForestDensity = climate.ForestDensity;
@@ -612,6 +511,138 @@ namespace Automap
612511
613512 return pngWriter;
614513 }
514+
515+ #endregion
516+
517+
518+ #region COPYPASTA
519+ //TODO: rewrite - with vertical ray caster, down to bottom-most chunk (for object detection...)
520+ //A partly re-written; ChunkMapLayer :: public int[] GenerateChunkImage(Vec2i chunkPos, IMapChunk mc)
521+ private void GenerateChunkImage(Vec2i chunkPos, IMapChunk mc, PngWriter pngWriter, out uint pixelCount)
522+ {
523+ pixelCount = 0;
524+ BlockPos tmpPos = new BlockPos( );
525+ Vec2i localpos = new Vec2i( );
526+ int chunkSize = ClientAPI.World.BlockAccessor.ChunkSize;
527+ var chunksColumn = new IWorldChunk[ClientAPI.World.BlockAccessor.MapSizeY / chunkSize];
528+
529+ int topChunkY = mc.YMax / chunkSize;//Heywaitaminute -- this isn't a highest FEATURE, if Rainmap isn't accurate!
530+ //Metadata of DateTime chunk was edited, chunk coords.,world-seed? Y-Max feature height
531+ //Grab a chunk COLUMN... Topmost Y down...
532+ for (int chunkY = 0; chunkY <= topChunkY; chunkY++) {
533+ chunksColumn[chunkY] = ClientAPI.World.BlockAccessor.GetChunk(chunkPos.X, chunkY, chunkPos.Y);
534+ //What to do if chunk is a void? invalid?
535+ }
536+
537+ // Prefetch map chunks, in pattern
538+ IMapChunk[ ] mapChunks = new IMapChunk[ ]
539+ {
540+ ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y - 1),
541+ ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y),
542+ ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X, chunkPos.Y - 1)
543+ };
544+
545+ //pre-create PNG line slices...
546+ ImageLine[ ] lines = Enumerable.Repeat(new object( ), chunkSize).Select(l => new ImageLine(pngWriter.ImgInfo)).ToArray( );
547+
548+ for (int posIndex = 0; posIndex < (chunkSize * chunkSize); posIndex++) {
549+ int mapY = mc.RainHeightMap[posIndex];
550+ int localChunkY = mapY / chunkSize;
551+ if (localChunkY >= (chunksColumn.Length)) continue;//Out of range!
552+
553+ MapUtil.PosInt2d(posIndex, chunkSize, localpos);
554+ int localX = localpos.X;
555+ int localZ = localpos.Y;
556+
557+ float b = 1;
558+ int leftTop, rightTop, leftBot;
559+
560+ IMapChunk leftTopMapChunk = mc;
561+ IMapChunk rightTopMapChunk = mc;
562+ IMapChunk leftBotMapChunk = mc;
563+
564+ int topX = localX - 1;
565+ int botX = localX;
566+ int leftZ = localZ - 1;
567+ int rightZ = localZ;
568+
569+ if (topX < 0 && leftZ < 0) {
570+ leftTopMapChunk = mapChunks[0];
571+ rightTopMapChunk = mapChunks[1];
572+ leftBotMapChunk = mapChunks[2];
573+ }
574+ else {
575+ if (topX < 0) {
576+ leftTopMapChunk = mapChunks[1];
577+ rightTopMapChunk = mapChunks[1];
578+ }
579+ if (leftZ < 0) {
580+ leftTopMapChunk = mapChunks[2];
581+ leftBotMapChunk = mapChunks[2];
582+ }
583+ }
584+
585+ topX = GameMath.Mod(topX, chunkSize);
586+ leftZ = GameMath.Mod(leftZ, chunkSize);
587+
588+ leftTop = leftTopMapChunk == null ? 0 : Math.Sign(mapY - leftTopMapChunk.RainHeightMap[leftZ * chunkSize + topX]);
589+ rightTop = rightTopMapChunk == null ? 0 : Math.Sign(mapY - rightTopMapChunk.RainHeightMap[rightZ * chunkSize + topX]);
590+ leftBot = leftBotMapChunk == null ? 0 : Math.Sign(mapY - leftBotMapChunk.RainHeightMap[leftZ * chunkSize + botX]);
591+
592+ float slopeness = (leftTop + rightTop + leftBot);
593+
594+ if (slopeness > 0) b = 1.2f;
595+ if (slopeness < 0) b = 0.8f;
596+
597+ b -= 0.15f; //Slope boost value
598+
599+ if (chunksColumn[localChunkY] == null) {
600+
601+ continue;
602+ }
603+
604+ chunksColumn[localChunkY].Unpack( );
605+ int blockId = chunksColumn[localChunkY].Blocks[MapUtil.Index3d(localpos.X, mapY % chunkSize, localpos.Y, chunkSize, chunkSize)];
606+
607+ Block block = ClientAPI.World.Blocks[blockId];
608+
609+ tmpPos.Set(chunkSize * chunkPos.X + localpos.X, mapY, chunkSize * chunkPos.Y + localpos.Y);
610+
611+ int avgCol = block.GetColor(ClientAPI, tmpPos);
612+ int rndCol = block.GetRandomColor(ClientAPI, tmpPos, BlockFacing.UP);
613+ int col = ColorUtil.ColorOverlay(avgCol, rndCol, 0.125f);
614+ var packedFormat = ColorUtil.ColorMultiply3Clamped(col, b);
615+
616+ int red = ColorUtil.ColorB(packedFormat);
617+ int green = ColorUtil.ColorG(packedFormat);
618+ int blue = ColorUtil.ColorR(packedFormat);
619+
620+
621+ //============ POI Population =================
622+ if (BlockID_Designators.ContainsKey(blockId)) {
623+ var desig = BlockID_Designators[blockId];
624+ red = desig.OverwriteColor.R;
625+ green = desig.OverwriteColor.G;
626+ blue = desig.OverwriteColor.B;
627+
628+ if (desig.SpecialAction != null) {
629+ desig.SpecialAction(ClientAPI, this.POIs, tmpPos, block);
630+ }
631+ }
632+
633+ ImageLineHelper.SetPixel(lines[localZ], localX, red, green, blue);
634+
635+ //chunkImage.SetPixel(localX, localZ, pixelColor);
636+ pixelCount++;
637+ }
638+
639+ for (int row = 0; row < pngWriter.ImgInfo.Rows; row++) {
640+ pngWriter.WriteRow(lines[row], row);
641+ }
642+
643+ pngWriter.End( );
644+ }
645+ #endregion
615646 }
616647
617648 }
\ No newline at end of file