Created
March 19, 2021 20:15
-
-
Save GuyInGrey/6a87475f8ec3017c1c1df23b64b3f0ea to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Drawing; | |
using System.Linq; | |
using System.Net; | |
using System.Runtime.InteropServices; | |
using System.Threading.Tasks; | |
using System.Windows.Forms; | |
using Flurl; | |
using Newtonsoft.Json; | |
namespace MapCreator | |
{ | |
public partial class MainForm : Form | |
{ | |
DynmapConfig Config; | |
string URL; | |
public MainForm() { InitializeComponent(); } | |
private void LoadBtn_Click(object sender, EventArgs e) | |
{ | |
// Add http to url if it's not already there | |
URL = urlBx.Text; | |
if (!URL.Contains("http")) { URL = "http://" + URL; urlBx.Text = URL; } | |
// Load /up/configuration of the dynmap | |
try | |
{ | |
using (var wc = new WebClient()) | |
{ | |
var cont = wc.DownloadString(Url.Combine(URL, "/up/configuration")); | |
Config = JsonConvert.DeserializeObject<DynmapConfig>(cont); | |
} | |
} | |
catch (Exception ex) | |
{ | |
MessageBox.Show("Either that url doesn't exist or it's not a dynmap.\n\n" + ex.Message, | |
"Failed to load map configuration.", MessageBoxButtons.OK); | |
return; | |
} | |
// Populate list box component on the form so that user can select which one they want | |
mapList.Items.Clear(); | |
foreach (var w in Config.Worlds) | |
{ | |
foreach (var m in w.Maps) | |
{ | |
mapList.Items.Add($"{w.Name}/{m.Name}"); | |
} | |
} | |
mapList.SelectedIndex = 0; | |
Console.WriteLine("Loaded map config."); | |
} | |
// Every time a change is made to the coordinates inputed, show a preview | |
private void MapList_SelectedIndexChanged(object _, EventArgs _2) { UpdatePreview(); } | |
private void Coordinate_ValueChanged(object _, EventArgs _2) { UpdatePreview(); } | |
public void UpdatePreview() | |
{ | |
Console.WriteLine("Updated preview."); | |
var selected = mapList.SelectedIndex; | |
if (selected == -1) { downloadBtn.Enabled = false; goto failed; } | |
downloadBtn.Enabled = true; | |
var sel = mapList.SelectedItem.ToString().Split('/'); | |
var world = sel[0]; | |
var mapName = sel[1]; | |
var map = Config.Worlds.First(w => w.Name == world).Maps.First(m => m.Name == mapName); | |
var minX = (float)Math.Min(x1Bx.Value, x2Bx.Value); | |
var maxX = (float)Math.Max(x1Bx.Value, x2Bx.Value); | |
var minZ = (float)Math.Min(z1Bx.Value, z2Bx.Value); | |
var maxZ = (float)Math.Max(z1Bx.Value, z2Bx.Value); | |
var tileMinX = (int)((minX * map.PixelsPerBlock) / 128); | |
var tileMaxX = (int)((maxX * map.PixelsPerBlock) / 128); | |
var tileMinZ = (int)((minZ * map.PixelsPerBlock) / 128); | |
var tileMaxZ = (int)((maxZ * map.PixelsPerBlock) / 128); | |
previewTL.Image = GetTile(tileMinX, tileMinZ, world, map.Prefix); | |
previewTR.Image = GetTile(tileMaxX, tileMinZ, world, map.Prefix); | |
previewBR.Image = GetTile(tileMaxX, tileMaxZ, world, map.Prefix); | |
previewBL.Image = GetTile(tileMinX, tileMaxZ, world, map.Prefix); | |
return; | |
failed:; | |
previewBL.Image = null; | |
previewBR.Image = null; | |
previewTL.Image = null; | |
previewTR.Image = null; | |
return; | |
} | |
// Get's a specified tile coordinate image | |
public Bitmap GetTile(int x, int z, string world, string mapPrefix) | |
{ | |
z = -z; | |
try | |
{ | |
var url = Url.Combine(URL, $"/tiles/{world}/{mapPrefix}/{x >> 5}_{z >> 5}/{x}_{z}.png"); | |
return new Bitmap(WebRequest.Create(url).GetResponse().GetResponseStream()); | |
} | |
catch (Exception e) | |
{ | |
MessageBox.Show("Failed to fetch tile: \n\n" + e.Message + "\n\n" + e.StackTrace); | |
return null; | |
} | |
} | |
// This is for showing/hiding the console while downloading is happening | |
[DllImport("kernel32.dll", ExactSpelling = true)] | |
public static extern IntPtr GetConsoleWindow(); | |
[DllImport("user32.dll")] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static extern bool SetForegroundWindow(IntPtr hWnd); | |
// Begin a tile range download | |
private void DownloadBtn_Click(object sender, EventArgs e) | |
{ | |
// Hide the form and show the console | |
Hide(); | |
SetForegroundWindow(GetConsoleWindow()); | |
Console.WriteLine("Starting..."); | |
// User picks location to save final exported image | |
var saveToPath = ""; | |
var dia = new SaveFileDialog() | |
{ | |
Filter = "PNG Image | *.png", | |
DefaultExt = "PNG", | |
FileName = "exportedMap.png", | |
}; | |
if (dia.ShowDialog() == DialogResult.OK) | |
{ | |
saveToPath = dia.FileName; | |
} | |
else { Show(); return; } | |
// Get the selected map's info | |
var selected = mapList.SelectedIndex; | |
if (selected == -1) { return; } | |
var sel = mapList.SelectedItem.ToString().Split('/'); | |
var world = sel[0]; | |
var mapName = sel[1]; | |
var map = Config.Worlds.First(w => w.Name == world).Maps.First(m => m.Name == mapName); | |
// Get the user-inputed world-coordinate range | |
var minX = (float)Math.Min(x1Bx.Value, x2Bx.Value); | |
var maxX = (float)Math.Max(x1Bx.Value, x2Bx.Value); | |
var minZ = (float)Math.Min(z1Bx.Value, z2Bx.Value); | |
var maxZ = (float)Math.Max(z1Bx.Value, z2Bx.Value); | |
// Convert world coordinates to tile coordinates. | |
// "PixelsPerBlock" is just the map's 'scale' property. | |
var tileMinX = (int)((minX * map.PixelsPerBlock) / 128); | |
var tileMaxX = (int)((maxX * map.PixelsPerBlock) / 128); | |
var tileMinZ = (int)((minZ * map.PixelsPerBlock) / 128); | |
var tileMaxZ = (int)((maxZ * map.PixelsPerBlock) / 128); | |
// Create bitmap to export | |
Bitmap final; | |
Graphics g; | |
var size = (128 * (tileMaxX - tileMinX), 128 * (tileMaxZ - tileMinZ)); // This is the final image size | |
try | |
{ | |
final = new Bitmap(size.Item1, size.Item2); | |
g = Graphics.FromImage(final); | |
} | |
catch | |
{ // This error happens when the user creates an export too large, generally a memory error | |
MessageBox.Show($"Failed to create final bitmap. You probably have a " + | |
$"selection that's too large.\n\nAttempted image size: {size.Item1}, {size.Item2}"); | |
Show(); | |
return; | |
} | |
var totalTiles = (tileMaxX - tileMinX) * (tileMaxZ - tileMinZ); | |
var tiles = new Dictionary<(int, int), Bitmap>(); | |
// Download all the tiles in the range, and save them to the to-export image | |
Console.WriteLine("Beginning download..."); | |
var start = DateTime.Now; | |
var i = 0; | |
for (var x = tileMinX; x <= tileMaxX; x++) | |
{ | |
Parallel.For(tileMinZ, tileMaxZ + 1, new ParallelOptions() { MaxDegreeOfParallelism = 16 }, z => | |
{ | |
tiles.Add((x, z), GetTile(x, z, world, map.Prefix)); | |
i++; | |
if (i % 100 == 0) | |
{ | |
var elapsed = DateTime.Now - start; | |
var ticksPerTile = elapsed.Ticks / i; | |
var endEstimate = ticksPerTile * (totalTiles - i) / 10000000L; | |
Console.WriteLine($"{i}/{totalTiles} - {endEstimate} seconds remaining"); | |
} | |
}); | |
} | |
Console.WriteLine("Download complete, creating final image..."); | |
foreach (var p in tiles) | |
{ | |
if (p.Value is null) { continue; } | |
g.DrawImage(p.Value, (p.Key.Item1 - tileMinX) * 128, (p.Key.Item2 - tileMinZ) * 128); | |
} | |
Console.WriteLine("Exporting to `" + saveToPath + "`..."); | |
final.Save(saveToPath); | |
Console.WriteLine("Done!"); | |
Show(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment