Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Build WorldMapper

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'

- name: Download TShock 6.1.0
run: |
mkdir -p libs tshock_extract
URL="https://github.com/Pryaxis/TShock/releases/download/v6.1.0/TShock-6.1.0-for-Terraria-1.4.5.6-win-x64-Release.zip"
curl -L --fail -o tshock.zip "$URL" || curl -L --fail -o tshock.zip "https://github.com/Pryaxis/TShock/releases/download/v6.1.0/TShock-6.1.0-for-Terraria-1.4.5.6-linux-x64-Release.zip"
unzip -q tshock.zip -d tshock_extract || true
find tshock_extract -type f \( -iname "*.dll" -o -iname "*.exe" \) -exec cp {} libs/ \;
cd libs
[ -f TSAPI.dll ] && [ ! -f TerrariaApi.Server.dll ] && cp TSAPI.dll TerrariaApi.Server.dll
[ ! -f TerrariaServer.exe ] && [ -f TShock.Server.exe ] && cp TShock.Server.exe TerrariaServer.exe
[ -f TShock.dll ] && [ ! -f TShockAPI.dll ] && cp TShock.dll TShockAPI.dll
ls -la

- run: dotnet restore

- run: dotnet build --configuration Release --no-restore

- uses: actions/upload-artifact@v4
with:
name: WorldMapper
path: bin/Release/net9.0/WorldMapper.dll
55 changes: 55 additions & 0 deletions WorldMapper.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AssemblyName>WorldMapper</AssemblyName>
<RootNamespace>WorldMapper</RootNamespace>
<Version>1.0.0.0</Version>
<LangVersion>latest</LangVersion>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<Reference Include="OTAPI">
<HintPath>libs\OTAPI.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="TerrariaApi.Server" Condition="Exists('libs\TerrariaApi.Server.dll')">
<HintPath>libs\TerrariaApi.Server.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="TSAPI" Condition="Exists('libs\TSAPI.dll')">
<HintPath>libs\TSAPI.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="TerrariaServer" Condition="Exists('libs\TerrariaServer.exe')">
<HintPath>libs\TerrariaServer.exe</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="TerrariaServer" Condition="Exists('libs\TerrariaServer.dll')">
<HintPath>libs\TerrariaServer.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="TShock.Server" Condition="Exists('libs\TShock.Server.dll')">
<HintPath>libs\TShock.Server.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="TShockAPI" Condition="Exists('libs\TShockAPI.dll')">
<HintPath>libs\TShockAPI.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ModFramework" Condition="Exists('libs\ModFramework.dll')">
<HintPath>libs\ModFramework.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SkiaSharp" Version="2.88.8" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.8" />
<PackageReference Include="MonoMod.RuntimeDetour" Version="25.0.0" />
</ItemGroup>

</Project>
16 changes: 0 additions & 16 deletions WorldMapper.sln

This file was deleted.

2 changes: 1 addition & 1 deletion WorldMapper/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ public class Config
public bool SaveMapOnWorldSave { get; set; }
public string MapFileNameFormat { get; set; } = "{0}_{1}_{2}.png";
}
}
}
41 changes: 12 additions & 29 deletions WorldMapper/DirectBitmap.cs
Original file line number Diff line number Diff line change
@@ -1,54 +1,37 @@
// wooooo stealing code https://stackoverflow.com/questions/24701703/c-sharp-faster-alternatives-to-setpixel-and-getpixel-for-bitmaps-for-windows-f
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.IO;
using SkiaSharp;

namespace WorldMapper
{
public class DirectBitmap : IDisposable
{
public Bitmap Bitmap { get; private set; }
public Int32[] Bits { get; private set; }
public bool Disposed { get; private set; }
public SKBitmap Bitmap { get; private set; }
public int Height { get; private set; }
public int Width { get; private set; }

protected GCHandle BitsHandle { get; private set; }

public DirectBitmap(int width, int height)
{
Width = width;
Height = height;
Bits = new Int32[width * height];
BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb,
BitsHandle.AddrOfPinnedObject());
Bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
}

public void SetPixel(int x, int y, Color colour)
public void SetPixel(int x, int y, SKColor colour)
{
int index = x + (y * Width);
int col = colour.ToArgb();

Bits[index] = col;
Bitmap.SetPixel(x, y, colour);
}

public Color GetPixel(int x, int y)
public void Save(string path)
{
int index = x + (y * Width);
int col = Bits[index];
Color result = Color.FromArgb(col);

return result;
using var data = Bitmap.Encode(SKEncodedImageFormat.Png, 100);
using var stream = File.OpenWrite(path);
data.SaveTo(stream);
}

public void Dispose()
{
if (Disposed) return;
Disposed = true;
Bitmap.Dispose();
BitsHandle.Free();
Bitmap?.Dispose();
}
}
}
}
11 changes: 5 additions & 6 deletions WorldMapper/MapGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using SkiaSharp;
using Terraria;
using Terraria.Map;

Expand All @@ -8,9 +9,7 @@ public static class MapGenerator
{
public static DirectBitmap Create(Rectangle? region = null)
{
Rectangle realRegion;
realRegion = region ?? new Rectangle(0, 0, Main.maxTilesX, Main.maxTilesY);

Rectangle realRegion = region ?? new Rectangle(0, 0, Main.maxTilesX, Main.maxTilesY);
var bitmap = new DirectBitmap(realRegion.Width, realRegion.Height);

var replacedMap = false;
Expand All @@ -27,9 +26,9 @@ public static DirectBitmap Create(Rectangle? region = null)
for (var y = 0; y < realRegion.Height; y++)
{
var tile = MapHelper.CreateMapTile(x + realRegion.X, y + realRegion.Y, byte.MaxValue);
var col = MapHelper.GetMapTileXnaColor(ref tile);
var col = MapHelper.GetMapTileXnaColor(tile);

bitmap.SetPixel(x, y, System.Drawing.Color.FromArgb(col.A, col.R, col.G, col.B));
bitmap.SetPixel(x, y, new SKColor(col.R, col.G, col.B, col.A));
}
}

Expand All @@ -38,4 +37,4 @@ public static DirectBitmap Create(Rectangle? region = null)
return bitmap;
}
}
}
}
66 changes: 48 additions & 18 deletions WorldMapper/Plugin.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using System;
using System.IO;
using System.Reflection;
using MonoMod.RuntimeDetour;
using Newtonsoft.Json;
using OTAPI;
using Terraria;
using Terraria.IO;
using TerrariaApi.Server;
using TShockAPI;

Expand All @@ -16,56 +17,85 @@ public class Plugin : TerrariaPlugin
public override string Author => "James Puleo";
public override string Description => "Generates a PNG map of the entire world";
public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;

private Config _config;
private Hook _saveHook;

public Plugin(Main game) : base(game)
{
}
public Plugin(Main game) : base(game) { }

public override void Initialize()
{
_config = File.Exists(Config.DefaultPath)
? JsonConvert.DeserializeObject<Config>(File.ReadAllText(Config.DefaultPath))
: new Config();

Hooks.World.IO.PostLoadWorld += OnPostLoadWorld;
Hooks.World.IO.PostSaveWorld += OnPostSaveWorld;
ServerApi.Hooks.GamePostInitialize.Register(this, OnPostInit);

Commands.ChatCommands.Add(new Command("worldmapper.generatemap", args =>
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

var fileName = args.Parameters.Count >= 1
? args.Parameters[0]
: string.Format(_config.MapFileNameFormat, Main.worldName, "manual", now);

using var bitmap = MapGenerator.Create();
Save(bitmap, fileName);
bitmap.Save(fileName);
TShock.Log.ConsoleInfo($"Map generated and saved as {fileName}");
args.Player.SendSuccessMessage($"Map saved as {fileName}");
}, "generatemap"));
}

private void OnPostSaveWorld(bool usecloudsaving, bool resettime)
private void OnPostInit(EventArgs args)
{
DoAutomaticGenerate("save");
if (_config.SaveMapOnWorldLoad)
DoAutomaticGenerate("load");

if (_config.SaveMapOnWorldSave)
HookWorldSave();
}

private void HookWorldSave()
{
try
{
var saveMethod = typeof(WorldFile).GetMethod("SaveWorld", new[] { typeof(bool), typeof(bool) });
if (saveMethod == null)
{
TShock.Log.ConsoleError("[WorldMapper] Could not find WorldFile.SaveWorld. Auto-save on world save disabled.");
return;
}

var hookMethod = typeof(Plugin).GetMethod(nameof(OnSaveWorld), BindingFlags.NonPublic | BindingFlags.Instance);
_saveHook = new Hook(saveMethod, hookMethod, this);
}
catch (Exception ex)
{
TShock.Log.ConsoleError($"[WorldMapper] Failed to hook world save: {ex.Message}");
}
}

private void OnPostLoadWorld(bool loadfromcloud)
private void OnSaveWorld(Action<bool, bool> orig, bool useCloudSaving, bool resetTime)
{
DoAutomaticGenerate("load");
orig(useCloudSaving, resetTime);
DoAutomaticGenerate("save");
}

private void DoAutomaticGenerate(string why)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
using var bitmap = MapGenerator.Create();
Save(bitmap, string.Format(_config.MapFileNameFormat, Main.worldName, why, now));
bitmap.Save(string.Format(_config.MapFileNameFormat, Main.worldName, why, now));
TShock.Log.ConsoleInfo($"Map auto-generated ({why})");
}

private static void Save(DirectBitmap bitmap, string fileName)
protected override void Dispose(bool disposing)
{
bitmap.Bitmap.Save(fileName);
TShock.Log.ConsoleInfo($"Map generated and saved as {fileName}");
if (disposing)
{
ServerApi.Hooks.GamePostInitialize.Deregister(this, OnPostInit);
_saveHook?.Dispose();
}
base.Dispose(disposing);
}
}
}
}
23 changes: 0 additions & 23 deletions WorldMapper/WorldMapper.csproj

This file was deleted.