diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..9807f42 --- /dev/null +++ b/.github/workflows/build.yml @@ -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 diff --git a/WorldMapper.csproj b/WorldMapper.csproj new file mode 100644 index 0000000..f50bd2d --- /dev/null +++ b/WorldMapper.csproj @@ -0,0 +1,55 @@ + + + + net9.0 + WorldMapper + WorldMapper + 1.0.0.0 + latest + disable + true + + + + + libs\OTAPI.dll + false + + + libs\TerrariaApi.Server.dll + false + + + libs\TSAPI.dll + false + + + libs\TerrariaServer.exe + false + + + libs\TerrariaServer.dll + false + + + libs\TShock.Server.dll + false + + + libs\TShockAPI.dll + false + + + libs\ModFramework.dll + false + + + + + + + + + + + diff --git a/WorldMapper.sln b/WorldMapper.sln deleted file mode 100644 index 504c355..0000000 --- a/WorldMapper.sln +++ /dev/null @@ -1,16 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorldMapper", "WorldMapper\WorldMapper.csproj", "{10880B32-0BD8-45B8-91F1-65A79DF520C9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {10880B32-0BD8-45B8-91F1-65A79DF520C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {10880B32-0BD8-45B8-91F1-65A79DF520C9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {10880B32-0BD8-45B8-91F1-65A79DF520C9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {10880B32-0BD8-45B8-91F1-65A79DF520C9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/WorldMapper/Config.cs b/WorldMapper/Config.cs index 06ec593..1a7bf9d 100644 --- a/WorldMapper/Config.cs +++ b/WorldMapper/Config.cs @@ -8,4 +8,4 @@ public class Config public bool SaveMapOnWorldSave { get; set; } public string MapFileNameFormat { get; set; } = "{0}_{1}_{2}.png"; } -} \ No newline at end of file +} diff --git a/WorldMapper/DirectBitmap.cs b/WorldMapper/DirectBitmap.cs index 7280159..89c47fe 100644 --- a/WorldMapper/DirectBitmap.cs +++ b/WorldMapper/DirectBitmap.cs @@ -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(); } } -} \ No newline at end of file +} diff --git a/WorldMapper/MapGenerator.cs b/WorldMapper/MapGenerator.cs index 38b83d6..e733e35 100644 --- a/WorldMapper/MapGenerator.cs +++ b/WorldMapper/MapGenerator.cs @@ -1,4 +1,5 @@ using Microsoft.Xna.Framework; +using SkiaSharp; using Terraria; using Terraria.Map; @@ -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; @@ -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)); } } @@ -38,4 +37,4 @@ public static DirectBitmap Create(Rectangle? region = null) return bitmap; } } -} \ No newline at end of file +} diff --git a/WorldMapper/Plugin.cs b/WorldMapper/Plugin.cs index d474be4..529876a 100644 --- a/WorldMapper/Plugin.cs +++ b/WorldMapper/Plugin.cs @@ -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; @@ -16,11 +17,11 @@ 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() { @@ -28,44 +29,73 @@ public override void Initialize() ? JsonConvert.DeserializeObject(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 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); } } -} \ No newline at end of file +} diff --git a/WorldMapper/WorldMapper.csproj b/WorldMapper/WorldMapper.csproj deleted file mode 100644 index 35e677e..0000000 --- a/WorldMapper/WorldMapper.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - net48 - latest - - - - - ..\..\tshock_test\Newtonsoft.Json.dll - - - ..\..\tshock_test\OTAPI.dll - - - ..\..\tshock_test\TerrariaServer.exe - - - ..\..\tshock_test\ServerPlugins\TShockAPI.dll - - - -