Skip to content

Instantly share code, notes, and snippets.

@GuyInGrey
Created March 19, 2021 20:15
Show Gist options
  • Save GuyInGrey/6a87475f8ec3017c1c1df23b64b3f0ea to your computer and use it in GitHub Desktop.
Save GuyInGrey/6a87475f8ec3017c1c1df23b64b3f0ea to your computer and use it in GitHub Desktop.
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